diff --git a/.github/workflows/asf-updates.yml b/.github/workflows/asf-updates.yml index 8185827abb111..46c419fee3cef 100644 --- a/.github/workflows/asf-updates.yml +++ b/.github/workflows/asf-updates.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Open Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 @@ -93,7 +93,8 @@ jobs: bin/release-helper.sh set-dep-ver awscli "==$AWSCLI_VERSION" # upgrade the requirements files only for the botocore package - pip install pip-tools + # FIXME remove pin on pip-tools when https://github.com/jazzband/pip-tools/issues/2215 us resolved + pip install "pip-tools<7.5.0" pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --extra base-runtime -o requirements-base-runtime.txt pyproject.toml pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --upgrade-package "awscli==$AWSCLI_VERSION" --extra runtime -o requirements-runtime.txt pyproject.toml pip-compile --strip-extras --upgrade-package "botocore==$BOTOCORE_VERSION" --upgrade-package "boto3==$BOTOCORE_VERSION" --upgrade-package "awscli==$AWSCLI_VERSION" --extra test -o requirements-test.txt pyproject.toml diff --git a/.github/workflows/aws-main.yml b/.github/workflows/aws-main.yml index 1185ce5249d1d..606179ab58b48 100644 --- a/.github/workflows/aws-main.yml +++ b/.github/workflows/aws-main.yml @@ -83,7 +83,7 @@ jobs: # default "disableCaching" to `false` if it's a push or schedule event disableCaching: ${{ inputs.disableCaching == true }} # default "disableTestSelection" to `true` if it's a push or schedule event - disableTestSelection: ${{ (inputs.enableTestSelection != '' && inputs.enableTestSelection) || github.event_name == 'push' }} + disableTestSelection: ${{ (inputs.enableTestSelection != '' && !inputs.enableTestSelection) || github.event_name == 'push' }} PYTEST_LOGLEVEL: ${{ inputs.PYTEST_LOGLEVEL }} forceARMTests: ${{ inputs.forceARMTests == true }} secrets: @@ -96,10 +96,11 @@ jobs: runs-on: ubuntu-latest needs: - test - if: ${{ inputs.onlyAcceptanceTests == false && inputs.enableTestSelection == false }} + # Do not push coverage data if only parts of the tests were executed + if: ${{ !(inputs.onlyAcceptanceTests == true || inputs.enableTestSelection == true || github.event_name == 'push') && !failure() && !cancelled() && github.repository == 'localstack/localstack' }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 @@ -113,7 +114,7 @@ jobs: run: make install-dev - name: Load all test results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-* path: target/coverage/ @@ -198,7 +199,7 @@ jobs: - test steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 diff --git a/.github/workflows/aws-tests-s3-image.yml b/.github/workflows/aws-tests-s3-image.yml index 729c0c3157403..4666231ca9485 100644 --- a/.github/workflows/aws-tests-s3-image.yml +++ b/.github/workflows/aws-tests-s3-image.yml @@ -95,7 +95,7 @@ jobs: runs-on: ${{ matrix.runner }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -173,12 +173,12 @@ jobs: ) steps: - name: Download AMD64 Results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-results-s3-image-amd64 - name: Download ARM64 Results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-results-s3-image-arm64 @@ -206,7 +206,7 @@ jobs: password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 @@ -217,12 +217,12 @@ jobs: run: pip install --upgrade setuptools setuptools_scm - name: Download AMD64 image - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: localstack-s3-image-amd64 - name: Download ARM64 image - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: localstack-s3-image-arm64 diff --git a/.github/workflows/aws-tests.yml b/.github/workflows/aws-tests.yml index 6bb60c7be4a31..5df4747c7a9b1 100644 --- a/.github/workflows/aws-tests.yml +++ b/.github/workflows/aws-tests.yml @@ -159,7 +159,7 @@ jobs: run: echo "PLATFORM=${{ (runner.arch == 'X64' && 'amd64') || (runner.arch == 'ARM64' && 'arm64') || '' }}" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: localstack # setuptools_scm requires the git history (at least until the last tag) to determine the version @@ -209,7 +209,7 @@ jobs: cloudformation-v2: ${{ steps.changes.outputs.cloudformation-v2 }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -267,7 +267,7 @@ jobs: events-v1: - 'tests/aws/services/events/**' cloudformation-v2: - - 'tests/aws/services/cloudformation/v2/**' + - 'tests/aws/services/cloudformation/**' - name: Run Unit Tests timeout-minutes: 8 @@ -308,7 +308,7 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-preflight @@ -317,7 +317,7 @@ jobs: if: success() || failure() with: files: | - test-results-preflight/*.xml + **/pytest-junit-*.xml check_name: "Test Results ${{ inputs.testAWSAccountId != '000000000000' && '(MA/MR) ' || ''}}- Preflight, Unit" test_file_prefix: "-/opt/code/localstack/" action_fail_on_inconclusive: true @@ -365,13 +365,13 @@ jobs: echo "${{ inputs.testEnvironmentVariables }}" | sed "s/;/\n/" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 - name: Download Lambda Common packages - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: lambda-common-${{ env.PLATFORM }} path: | @@ -384,7 +384,7 @@ jobs: - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-selection path: dist/testselection/ @@ -394,7 +394,7 @@ jobs: env: # add the GitHub API token to avoid rate limit issues GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }} --splits 4 --group ${{ matrix.group }} --store-durations --clean-durations --ignore=tests/unit/ --ignore=tests/bootstrap --ignore=tests/aws/services/cloudformation/v2" + PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }} --splits 4 --group ${{ matrix.group }} --store-durations --clean-durations --ignore=tests/unit/ --ignore=tests/bootstrap" COVERAGE_FILE: "target/.coverage.integration-${{ env.PLATFORM }}-${{ matrix.group }}" JUNIT_REPORTS_FILE: "target/pytest-junit-integration-${{ env.PLATFORM }}-${{ matrix.group }}.xml" DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_PULL_USERNAME }} @@ -452,7 +452,7 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -511,13 +511,13 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Bootstrap Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 if: ${{ matrix.arch == 'amd64' }} with: pattern: test-results-bootstrap - name: Download Integration Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-integration-${{ matrix.arch }}-* @@ -572,7 +572,7 @@ jobs: echo "${{ inputs.testEnvironmentVariables }}" | sed "s/;/\n/" >> $GITHUB_ENV - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 @@ -631,7 +631,7 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Acceptance Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-acceptance-${{ matrix.arch }} @@ -659,14 +659,14 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-selection path: dist/testselection/ @@ -709,14 +709,14 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-selection path: dist/testselection/ @@ -758,14 +758,14 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-selection path: dist/testselection/ @@ -809,24 +809,24 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Prepare Local Test Environment uses: ./.github/actions/setup-tests-env - name: Download Test Selection if: ${{ env.TESTSELECTION_PYTEST_ARGS }} - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-selection path: dist/testselection/ - name: Run CloudFormation Engine v2 Tests - timeout-minutes: 30 + timeout-minutes: 60 env: # add the GitHub API token to avoid rate limit issues GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TEST_PATH: "tests/aws/services/cloudformation/v2" + TEST_PATH: "tests/aws/services/cloudformation" PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }} --reruns 3 -o junit_suite_name='cloudformation_v2'" PROVIDER_OVERRIDE_CLOUDFORMATION: "engine-v2" run: make test-coverage @@ -859,22 +859,22 @@ jobs: if: always() && !cancelled() && !contains(needs.*.result, 'skipped') steps: - name: Download Cloudformation v2 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-cloudformation-v2 - name: Download EventBridge v1 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-events-v1 - name: Download DynamoDB v2 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-dynamodb-v2 - name: Download CloudWatch v1 Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: test-results-cloudwatch-v1 @@ -905,7 +905,7 @@ jobs: password: ${{ secrets.DOCKERHUB_PULL_TOKEN }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # setuptools_scm requires the git history (at least until the last tag) to determine the version fetch-depth: 0 diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index e8178420788aa..2ffcb0cbfa4c7 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -13,7 +13,7 @@ jobs: name: Sync DockerHub Description runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Docker Hub Description uses: peter-evans/dockerhub-description@v4 diff --git a/.github/workflows/marker-report.yml b/.github/workflows/marker-report.yml index 8e38e3c670417..a22df3d64b6be 100644 --- a/.github/workflows/marker-report.yml +++ b/.github/workflows/marker-report.yml @@ -38,7 +38,7 @@ jobs: timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/tests-bin.yml b/.github/workflows/tests-bin.yml index 6b214984659fc..4faa21eb9dc05 100644 --- a/.github/workflows/tests-bin.yml +++ b/.github/workflows/tests-bin.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup BATS run: | diff --git a/.github/workflows/tests-cli.yml b/.github/workflows/tests-cli.yml index c5215f4fed257..81a14a0f18cab 100644 --- a/.github/workflows/tests-cli.yml +++ b/.github/workflows/tests-cli.yml @@ -84,7 +84,7 @@ jobs: CI_JOB_ID: ${{ github.job }}-${{ matrix.python-version }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python id: setup-python uses: actions/setup-python@v5 diff --git a/.github/workflows/tests-podman.yml b/.github/workflows/tests-podman.yml index 778fb5216b174..fc9da7d48dd72 100644 --- a/.github/workflows/tests-podman.yml +++ b/.github/workflows/tests-podman.yml @@ -37,7 +37,7 @@ jobs: CI_JOB_ID: ${{ github.job }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/tests-pro-integration.yml b/.github/workflows/tests-pro-integration.yml index d97f74beab71c..8dd7ab9d74fed 100644 --- a/.github/workflows/tests-pro-integration.yml +++ b/.github/workflows/tests-pro-integration.yml @@ -135,7 +135,7 @@ jobs: docker-images: false - name: Checkout Community - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: localstack fetch-depth: 0 # we need the additional commits to figure out the merge base for test selection @@ -198,7 +198,7 @@ jobs: return DEFAULT_REF - name: Checkout Pro - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: repository: localstack/localstack-pro ref: ${{steps.determine-companion-ref.outputs.result}} @@ -339,7 +339,7 @@ jobs: AWS_DEFAULT_REGION: "us-east-1" JUNIT_REPORTS_FILE: "pytest-junit-community-${{ matrix.group }}.xml" TEST_PATH: "../../localstack/tests/aws/" # TODO: run tests in tests/integration - PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }}--splits ${{ strategy.job-total }} --group ${{ matrix.group }} --durations-path ../../localstack/.test_durations --store-durations --ignore ../../localstack/tests/aws/services/cloudformation/v2" + PYTEST_ARGS: "${{ env.TINYBIRD_PYTEST_ARGS }}${{ env.TESTSELECTION_PYTEST_ARGS }}--splits ${{ strategy.job-total }} --group ${{ matrix.group }} --durations-path ../../localstack/.test_durations --store-durations" working-directory: localstack-pro run: | # Remove the host tmp folder (might contain remnant files with different permissions) @@ -382,12 +382,12 @@ jobs: ) steps: - name: Download Artifacts 1 - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-results-community-1 - name: Download Artifacts 2 - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: test-results-community-2 diff --git a/.github/workflows/update-test-durations.yml b/.github/workflows/update-test-durations.yml index 573f21c6b0fd6..72edd58a4051f 100644 --- a/.github/workflows/update-test-durations.yml +++ b/.github/workflows/update-test-durations.yml @@ -22,7 +22,7 @@ jobs: name: "Download, merge and create PR with test durations" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: path: localstack @@ -37,7 +37,7 @@ jobs: echo "AWS_MAIN_LATEST_SCHEDULED_RUN_ID=${latest_run_id}" >> $GITHUB_ENV - name: Load test durations - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: pattern: pytest-split-durations-${{ env.PLATFORM }}-* path: artifacts-test-durations diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml index 64ab360af7ed2..55cd6483b1951 100644 --- a/.github/workflows/validate-codeowners.yml +++ b/.github/workflows/validate-codeowners.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Validate codeowners uses: mszostok/codeowners-validator@v0.7.4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99a259f1559d5..5ee7cc392573d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.12.5 + rev: v0.12.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -11,14 +11,14 @@ repos: - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.17.0 + rev: v1.17.1 hooks: - id: mypy entry: bash -c 'cd localstack-core && mypy --install-types --non-interactive' additional_dependencies: ['botocore-stubs', 'rolo'] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace diff --git a/.test_durations b/.test_durations index 17e82f5f0d57d..d42c9e4098ada 100644 --- a/.test_durations +++ b/.test_durations @@ -1,4815 +1,4960 @@ { - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_lambda_dynamodb": 1.9658959930001174, - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_opensearch_crud": 3.435174576999941, - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_search_books": 60.710861663, - "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_setup": 92.62244867399997, - "tests/aws/scenario/kinesis_firehose/test_kinesis_firehose.py::TestKinesisFirehoseScenario::test_kinesis_firehose_s3": 0.002614400000027217, - "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_destination_sns": 5.60948553999998, - "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_infra": 12.240709726000034, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_prefill_dynamodb_table": 29.09557482200006, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]": 3.9011395640000046, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]": 2.8125879660000237, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input2-FAILED]": 0.9402435910000122, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input3-FAILED]": 0.6784016249999922, - "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input4-FAILED]": 0.5093364669999687, - "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_deployed_infra_state": 0.0028881280000518927, - "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_populate_data": 0.0016716179999889391, - "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_user_clicks_are_stored": 0.0017432509999935064, - "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_notes_rest_api": 4.537658201999989, - "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_validate_infra_setup": 34.03103878999997, - "tests/aws/services/acm/test_acm.py::TestACM::test_boto_wait_for_certificate_validation": 1.1287596460000486, - "tests/aws/services/acm/test_acm.py::TestACM::test_certificate_for_subdomain_wildcard": 2.2366661329999715, - "tests/aws/services/acm/test_acm.py::TestACM::test_create_certificate_for_multiple_alternative_domains": 11.207225972000003, - "tests/aws/services/acm/test_acm.py::TestACM::test_domain_validation": 0.306542984000032, - "tests/aws/services/acm/test_acm.py::TestACM::test_import_certificate": 0.965256203000024, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiAuthorizer::test_authorizer_crud_no_api": 0.033051800999942316, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_doc_parts_crud_no_api": 0.0325181290000387, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_documentation_part_lifecycle": 0.06888426699993033, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts": 0.1240935849999687, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_create_documentation_part_operations": 0.03848816600003602, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_delete_documentation_part": 0.05036984499992059, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_part": 0.04410347200007436, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_parts": 0.014249382999992122, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_update_documentation_part": 0.051241709000009905, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_lifecycle": 0.07388209399999823, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_request_parameters": 0.049647582999966744, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_model": 0.2810050250000131, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_validation": 0.07467527299991161, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method": 0.07276314399996409, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method_validation": 0.1349508390000551, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_lifecycle": 0.07099860799996804, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_validation": 0.09988646800002243, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_update_model": 0.07347679800005835, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_create_request_validator_invalid_api_id": 0.01584852699994599, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_delete_request_validator": 0.042822649999948226, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validator": 0.044354959000088456, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validators": 0.014752499999985957, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_update_request_validator_operations": 0.06163765299999113, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_request_validator_lifecycle": 0.09228683700001739, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_validators_crud_no_api": 0.035259765000034804, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource": 0.11606050399996093, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource_validation": 0.07759795900000199, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_resource_parent_invalid": 0.031525047999991784, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_delete_resource": 0.06939277100002528, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_resource_lifecycle": 0.10697966500003986, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_behaviour": 0.14560039500003086, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_private_type": 0.030371676000015668, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_verify_defaults": 0.08101343900000302, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_binary_media_types": 0.027056547999961822, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_endpoint_configuration": 0.10162018500000158, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_optional_params": 0.07351876399997082, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_tags": 0.047299131999920974, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_get_api_case_insensitive": 0.001819322000073953, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_list_and_delete_apis": 0.08516098199999078, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_behaviour": 0.05359278000003087, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_compression": 0.09156979499994122, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_concatenation_of_errors": 0.0018621509999547925, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_invalid_api_id": 0.014744676000020718, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_ip_address_type": 0.07564396799995166, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_operation_add_remove": 0.05168826899995338, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_crud": 0.09937602000002244, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_put": 0.09921288299995012, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_validation": 0.10404522600003929, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_update_gateway_response": 0.12406722299999728, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": 0.037480163999987326, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_responsetemplates": 0.0018845330000090144, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_statuscode": 0.037121579000029215, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_api": 0.02469453299994484, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_method": 0.03932143899999119, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_resource": 0.03812018999991551, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_status_code": 0.047109441999964474, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_integration_response": 0.09572948200002429, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_method_response": 0.09031190600001082, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_request_parameter_bool_type": 0.0018460619999700612, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": 0.07367277600002353, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": 0.04049201800000901, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_lack_response_parameters_and_models": 0.07519914199997402, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response": 0.06369711400003553, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_negative_tests": 0.08969188099990788, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_wrong_operations": 0.08795898000005309, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_wrong_param_names": 0.08468058900001552, - "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_invoke_test_method": 0.19254535299995723, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_account": 0.04320282699995914, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_authorizer_crud": 0.0018782509999937247, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_handle_domain_name": 0.2498313990000156, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_http_integration_with_path_request_parameter": 0.0018933679999690867, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_asynchronous_invocation": 1.3035136820000162, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_integration_aws_type": 7.734469123999986, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/foo1]": 0.0017542010000397568, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/{test_param1}]": 0.0018024810000270008, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method": 0.0016964639999628162, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method_with_path_param": 0.0017193360000078428, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_with_is_base_64_encoded": 0.001721840999948654, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_mock_integration": 0.06520759500000395, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_mock_integration_response_params": 0.002101025000115442, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigateway_with_custom_authorization_method": 15.357900554999958, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[dev]": 1.6811695059999465, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[local]": 1.6142841560000534, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_test_invoke_method_api": 2.2027928989999737, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping": 0.17768989900014276, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping_root": 0.1565066640000623, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[host_based_url]": 0.061993368999992526, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[localstack_path_based_url]": 0.063610763999975, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[path_based_url]": 0.06531057400002283, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_delete_rest_api_with_invalid_id": 0.012420897000026798, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.HOST_BASED]": 0.07302876199997854, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.LS_PATH_BASED]": 0.07347622999998293, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.PATH_BASED]": 0.07100565799999004, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.HOST_BASED]": 0.09459531000004517, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.LS_PATH_BASED]": 0.06870420000001332, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.PATH_BASED]": 0.06802737999998953, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.HOST_BASED]": 0.07148151299998062, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.LS_PATH_BASED]": 0.07729620100008106, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.PATH_BASED]": 0.07387222599999177, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.HOST_BASED]": 0.06846372799998335, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.LS_PATH_BASED]": 0.07027082799999107, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.PATH_BASED]": 0.06939075799999728, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_multiple_api_keys_validate": 0.44590718100005233, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_with_request_template": 0.001688509000018712, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_without_request_template": 0.0018184689999998227, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_response_headers_invocation_with_apigw": 1.7450040979999812, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_update_rest_api_deployment": 0.07175922999999784, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[custom]": 0.001791820999983429, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[proxy]": 0.001748079000094549, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-GET]": 0.09486964099994566, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-POST]": 0.09469750799996746, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-GET]": 0.10960637799996675, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-POST]": 0.0958309449998751, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-GET]": 0.11887853900009304, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-POST]": 0.12365711500001453, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-GET]": 0.12157701900002849, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-POST]": 0.12642133700012437, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-GET]": 0.09316602699993837, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-POST]": 0.09263541999985136, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-GET]": 0.09356879099993876, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-POST]": 0.096322027000042, - "tests/aws/services/apigateway/test_apigateway_basic.py::TestTagging::test_tag_api": 0.08852044699995076, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_apigw_call_api_with_aws_endpoint_url": 0.01588612299997294, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-ANY]": 4.1609581859999025, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-GET]": 3.371756303999973, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-ANY]": 3.353133064000076, - "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-GET]": 9.517751571000076, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestCanaryDeployments::test_invoking_canary_deployment": 0.12324679499988633, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment": 0.12247360299988941, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_by_stage_update": 0.1343767130000515, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_validation": 0.0924865529999579, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_with_stage": 0.10506890300007399, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_update_stages": 0.14722061899999517, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_canary_deployment_validation": 0.15186355899993487, - "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_with_copy_ops": 0.13257302900001378, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator": 2.377756869000109, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_models": 0.17082527200000186, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_one_ofmodels": 0.18845078100002866, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_body_formatting": 3.5163204749999295, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_path_template_formatting": 0.6199991370000362, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_integration_request_parameters_mapping": 0.10761376000016298, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_invocation_trace_id": 2.284794003000002, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_api_not_existing": 0.02462630100012575, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_proxy_routing_with_hardcoded_resource_sibling": 0.2021082320000005, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_not_found": 0.11518773600005261, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_custom_api_id": 0.09618492399999923, - "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_hardcoded_resource_sibling_order": 0.19020799099996566, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[False]": 0.40800758900002165, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[True]": 0.4396916670000337, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_update_deployments": 0.326688890000014, - "tests/aws/services/apigateway/test_apigateway_common.py::TestDocumentations::test_documentation_parts_and_versions": 0.11036637800020799, - "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_create_update_stages": 0.32249944099987715, - "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_update_stage_remove_wildcard": 0.30901139899992813, - "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_api_key_required_for_methods": 0.19580672799997956, - "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_usage_plan_crud": 0.19161713099992994, - "tests/aws/services/apigateway/test_apigateway_custom_ids.py::test_apigateway_custom_ids": 0.05910293100009767, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_error_aws_proxy_not_supported": 0.14524141799995505, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[PutItem]": 0.3725421220000271, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Query]": 0.44348953799999435, - "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Scan]": 0.3491718429999082, - "tests/aws/services/apigateway/test_apigateway_eventbridge.py::test_apigateway_to_eventbridge": 0.21605546399996456, - "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_api_keys": 0.1556478090001292, - "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_usage_plan_api_keys": 0.15707564499996352, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_create_domain_names": 0.07398107399990295, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.40025349000006827, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETS]": 0.3100366799999392, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.4057235520000404, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETS]": 0.3122011229999089, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_name": 0.06958948099997997, - "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_names": 0.07176444499987156, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP]": 1.748026516000209, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP_PROXY]": 1.741234360999897, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP]": 2.05011000900015, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP_PROXY]": 2.019267971000204, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP]": 2.1555608629998915, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP_PROXY]": 2.214970807999862, - "tests/aws/services/apigateway/test_apigateway_http.py::test_http_proxy_integration_request_data_mappings": 2.037629436999964, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[openapi.spec.tf.json]": 0.3594950400000698, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[swagger-mock-cors.json]": 0.42388183499997467, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api": 0.06624614699990161, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[ignore]": 0.8646778810000342, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[prepend]": 1.7313012839998692, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[split]": 0.8749221169999828, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[ignore]": 0.6001682610000216, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[prepend]": 0.6022758100000374, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[split]": 0.595040987999937, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_swagger_api": 0.7615011800000957, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models": 0.2793969920002155, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models_and_request_validation": 0.3845796519999567, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_cognito_auth_identity_source": 0.40187553299995216, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_global_api_key_authorizer": 0.28094280099992375, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_http_method_integration": 0.289152520000016, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_integer_http_status_code": 0.2249898799998391, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_stage_variables": 1.6897076299999298, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[merge]": 0.33726757499994164, - "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[overwrite]": 0.34483216499995706, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS]": 2.4000383960001272, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS_PROXY]": 2.4229846660000476, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP]": 0.8103909309999153, - "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP_PROXY]": 0.8176825689999987, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": 5.623029853999924, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_http_integration_status_code_selection": 0.12111905000006118, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_path_param": 0.09462100200005352, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_request_overrides_in_response_template": 0.11671210499991957, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[False]": 0.08527618699997674, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[True]": 0.08516853699995863, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_vtl_map_assignation": 0.08624700100006066, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_response_with_response_template": 1.1998605899999575, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_responses": 0.17063735699991867, - "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_validation": 0.2000090869998985, - "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecord]": 1.0985163540000258, - "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecords]": 1.0887584829999923, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_binary_response": 3.714412637999999, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_response_payload_format_validation": 3.9828786240000227, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration": 1.6787915290000228, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_response_with_mapping_templates": 1.8418140830000311, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_with_request_template": 2.7283690199999455, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration": 4.013478568000096, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_non_post_method": 1.2798216090000096, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_request_data_mapping": 2.768030678000059, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_response_format": 1.967856296999912, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_rust_proxy_integration": 3.8190613670000175, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_selection_patterns": 2.000187760000017, - "tests/aws/services/apigateway/test_apigateway_lambda.py::test_put_integration_aws_proxy_uri": 1.2844049320000295, - "tests/aws/services/apigateway/test_apigateway_lambda_cfn.py::TestApigatewayLambdaIntegration::test_scenario_validate_infra": 7.639341964999858, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[CONVERT_TO_TEXT]": 0.5103502540000591, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[None]": 0.5020980819998613, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary": 0.454437854000048, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary_with_request_template": 0.28275198200014984, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary": 0.5068814649999922, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary_with_request_template": 0.31151203100012026, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_text": 0.5174195019999388, - "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_no_content_handling": 0.5153542180000841, - "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_any": 0.4110278970000536, - "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_method_mapping": 0.4561227259999896, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_amz_json_protocol": 1.01779735599996, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration": 1.1553835980000713, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttribute]": 0.242232227000045, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttributes]": 0.23674604600012117, - "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_request_and_response_xml_templates_integration": 0.334569450999993, - "tests/aws/services/apigateway/test_apigateway_ssm.py::test_get_parameter_query_protocol": 0.0020445310001377948, - "tests/aws/services/apigateway/test_apigateway_ssm.py::test_ssm_aws_integration": 0.22586156500005927, - "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[DeleteStateMachine]": 1.3490051369999492, - "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[StartExecution]": 1.426715578999847, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_api_exceptions": 0.0017162400000643174, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_exceptions": 0.0017400550000274961, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_invalid_desiredstate": 0.0018061470000247937, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_double_create_with_client_token": 0.001825544000098489, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_lifecycle": 0.0019656099999565413, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources": 0.0016870660000449789, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources_with_resource_model": 0.0017493430000286025, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_update": 0.0018405110000685454, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[FAIL]": 0.001836796000020513, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[SUCCESS]": 0.0018462929999714106, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_request": 0.001900060999901143, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_get_request_status": 0.0017383720000907488, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_invalid_request_token_exc": 0.002120702000070196, - "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_list_request_status": 0.0017402150000407346, - "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_deleting_resource": 0.00181181799996466, - "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_single_resource": 4.199325971999997, - "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": 0.0019145780000826562, - "tests/aws/services/cloudformation/api/test_changesets.py::test_autoexpand_capability_requirement": 0.08277968899994903, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_non_supported_resource_change_set": 22.889103094999882, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_supported_resource_change_set": 20.60475641000005, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": 2.143559339000035, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_create_existing": 0.0016879380000318633, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_invalid_params": 0.015139941000029467, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_missing_stackname": 0.004609298999980638, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_nonexisting": 0.015291683999976158, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_without_parameters": 0.0018712290000166831, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_with_ssm_parameter": 1.1482915249999905, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_without_parameters": 0.09173076499996569, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_with_stack_id": 0.2434602849999692, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_delete_create": 2.1700406919999295, - "tests/aws/services/cloudformation/api/test_changesets.py::test_create_while_in_review": 0.0019012359999805994, - "tests/aws/services/cloudformation/api/test_changesets.py::test_delete_change_set_exception": 0.021217006000142646, - "tests/aws/services/cloudformation/api/test_changesets.py::test_deleted_changeset": 0.04879442200012818, - "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_nonexisting": 0.01329124399990178, - "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": 0.04838235900001564, - "tests/aws/services/cloudformation/api/test_changesets.py::test_empty_changeset": 1.3190058399999316, - "tests/aws/services/cloudformation/api/test_changesets.py::test_execute_change_set": 0.0018180700000129946, - "tests/aws/services/cloudformation/api/test_changesets.py::test_multiple_create_changeset": 0.35355509700002585, - "tests/aws/services/cloudformation/api/test_changesets.py::test_name_conflicts": 1.9038876990000517, - "tests/aws/services/cloudformation/api/test_drift_detection.py::test_drift_detection_on_lambda": 0.0017792670000744693, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": 0.0017584179998948457, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": 0.0017670849999831262, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": 0.0017518259999178554, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": 0.001892378000093231, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": 0.001872039999966546, - "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": 0.0017411070000434847, - "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": 0.0018454700000347657, - "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": 0.001870245999953113, - "tests/aws/services/cloudformation/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": 0.0018357529999093458, - "tests/aws/services/cloudformation/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": 0.0018724010000141789, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": 6.261432998000146, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_lifecycle_nested_stack": 0.002104781999946681, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_output_in_params": 12.632075070999917, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack": 6.210120931000006, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack_output_refs": 6.220735287000139, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stacks_conditions": 6.2563296390000005, - "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_with_nested_stack": 12.311293133999925, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": 2.1044887589999917, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": 2.1018363379998846, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_reference_unsupported_resource": 2.107892007000032, - "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_sub_resolving": 2.106234014000165, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": 0.0017480589998513096, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": 0.002006701000027533, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": 0.0017508540000790163, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": 0.001735535999955573, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": 0.0018279580000353235, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": 0.0017556140001033782, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": 0.0018490169999267891, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource0]": 0.0016903719999845634, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource1]": 0.001729806999946959, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_modifying_with_policy_specifying_resource_id": 0.001843215000121745, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_replacement": 0.0017515160000129981, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": 0.001753450000023804, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": 0.001738853000006202, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::S3::Bucket]": 0.0017174529999692822, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::SNS::Topic]": 0.001703175999978157, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": 0.001827538000156892, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": 0.0018198530000290702, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": 0.0017272699999466568, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": 0.001749060999941321, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": 0.0017936949999466378, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_empty_policy": 0.0017358459999741171, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[False]": 0.0017352250000612912, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[True]": 0.001778587000103471, - "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_policy": 0.0017510559999891484, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_create_stack_with_custom_id": 1.0665487400000302, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[False-0]": 0.002358954999976959, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[True-1]": 0.0020451420001563747, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": 0.002895531999911327, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": 0.001738701000022047, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": 2.104769936999901, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": 2.1064562410000462, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": 1.055264237000074, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": 1.058268506999866, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": 2.181459592999886, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_stack_resources_for_removed_resource": 19.16629370399994, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": 2.288754615000016, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": 4.368783993000079, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_name_creation": 0.07728306000012708, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_update_resources": 4.432269957000017, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_actual_update": 4.177895979000027, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": 2.0903690149999647, - "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": 2.266701055999988, - "tests/aws/services/cloudformation/api/test_stacks.py::test_blocked_stack_deletion": 0.0018946019999930286, - "tests/aws/services/cloudformation/api/test_stacks.py::test_describe_stack_events_errors": 0.022298699000089073, - "tests/aws/services/cloudformation/api/test_stacks.py::test_events_resource_types": 2.1454409259998783, - "tests/aws/services/cloudformation/api/test_stacks.py::test_linting_error_during_creation": 0.0017930729999307005, - "tests/aws/services/cloudformation/api/test_stacks.py::test_list_parameter_type": 2.106541148999895, - "tests/aws/services/cloudformation/api/test_stacks.py::test_name_conflicts": 2.386898768000151, - "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": 3.8792626189999737, - "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": 0.014889347999996971, - "tests/aws/services/cloudformation/api/test_stacks.py::test_notifications": 0.0017055000000709697, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-B-C]": 2.384217020000051, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-C-B]": 2.391974201999915, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-A-C]": 2.3821009189999813, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-C-A]": 3.385830337000016, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-A-B]": 2.377239292000013, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-B-A]": 2.3837746079998396, - "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_resource_not_found": 2.096905295000056, - "tests/aws/services/cloudformation/api/test_stacks.py::test_update_termination_protection": 2.128387840000073, - "tests/aws/services/cloudformation/api/test_stacks.py::test_updating_an_updated_stack_sets_status": 6.366796449999924, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": 1.145606073999943, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": 0.12074120500005847, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": 1.137814024000022, - "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": 0.09474466400001802, - "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary": 2.2454653219999727, - "tests/aws/services/cloudformation/api/test_templates.py::test_validate_invalid_json_template_should_fail": 0.09346096299987039, - "tests/aws/services/cloudformation/api/test_templates.py::test_validate_template": 0.09322609999992437, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach": 1.2871027840000124, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_multiple_resources": 1.3804343729999573, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_use_case": 1.5464534519999233, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_length": 1.2743411729999252, - "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_to_json_string": 1.2853135410000505, - "tests/aws/services/cloudformation/api/test_transformers.py::test_duplicate_resources": 2.347456683000132, - "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_individual_resource_level": 2.2382948929999884, - "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_property_level": 2.2801014490000853, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_basic_update": 3.1282431359999237, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_diff_after_update": 3.148156761999985, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_parameters_update": 3.1284689309999294, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_template_error": 0.0017646900000727328, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_set_notification_arn_with_update": 0.001750563999962651, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_tags": 0.00184006999995745, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_using_template_url": 3.2013953569999103, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability0]": 0.001750472999901831, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability1]": 0.0018333179999672211, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": 0.0017616730000327152, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": 3.1253762629999073, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_template": 0.0019600729999638133, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_resource_types": 0.001901484999962122, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_role_without_permissions": 0.001976703999957863, - "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_rollback_configuration": 0.0017343739999660102, - "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[missing-def]": 0.0018005860000585017, - "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[multiple-nones]": 0.001954453000053036, - "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[none-value]": 0.0018098739999459212, - "tests/aws/services/cloudformation/api/test_validations.py::test_missing_resources_block": 0.001790368000001763, - "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[invalid-key]": 0.0017312180000317312, - "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[missing-type]": 0.0017601429999558604, - "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": 2.1216704900000423, - "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": 0.001686543999994683, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_condition_on_outputs": 2.1142234929999404, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[create]": 2.1393232139998872, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[no-create]": 2.122025780000172, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[dev-us-west-2]": 2.1032747250001194, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[production-us-east-1]": 2.1016941089999364, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_with_select": 2.1021599199999628, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[None-FallbackParamValue]": 2.1303020269999706, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[false-DefaultParamValue]": 2.1359146089999967, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[true-FallbackParamValue]": 2.138788530000056, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": 0.0018182500000420987, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_intrinsic_fn_condition": 0.0017285740000261285, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_with_macro": 0.0017282329999943613, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": 0.0017430200000490004, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": 0.001730044999931124, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": 0.0017502050000075542, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": 0.001710769000055734, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": 0.0017471590000468495, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_deploys_resource": 2.108331082999939, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_doesnt_deploy_resource": 0.0851443010001276, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[nope]": 2.0957527529998288, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[yep]": 2.091675005999946, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_sub_in_conditions": 2.123664410999936, - "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": 4.233097606999877, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level": 2.0802713339999173, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level": 2.0750957830000516, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": 2.096697038999878, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": 0.001851030000011633, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": 0.0018520339999668067, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": 2.11545809200004, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": 2.0993784799999275, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": 0.0018997609998905318, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": 0.0018192920000501545, - "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_simple_mapping_working": 2.1104293040000357, - "tests/aws/services/cloudformation/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": 0.0019797789999529414, - "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_fn_sub_cases": 2.114312756000004, - "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": 2.1085067309999204, - "tests/aws/services/cloudformation/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": 2.1267248489998565, - "tests/aws/services/cloudformation/engine/test_references.py::test_useful_error_when_invalid_ref": 0.016672502000119493, - "tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/test_basic.py::TestBasicCRD::test_black_box": 2.566077512999982, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": 2.3954563370000415, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": 7.209457596999982, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_security_group_with_tags": 2.1098518409999087, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": 2.508073618000026, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_autogenerated_values": 2.100214468999866, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_black_box": 2.1377949769999987, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_getatt": 2.137621827999965, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestUpdates::test_update_without_replacement": 0.0017790880000347897, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Arn]": 0.001725847999978214, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Id]": 0.001950916999930996, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Path]": 0.0017235539999091998, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[PermissionsBoundary]": 0.0017338830000426242, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[UserName]": 0.0017245459999912782, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": 2.2562247790000356, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_cfn_handle_iam_role_resource_no_role_name": 2.1369265419999692, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_delete_role_detaches_role_policy": 4.214670837999847, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_user_access_key": 4.205948388000138, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_username_defaultname": 2.1817599990000645, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": 2.4723468529999764, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_policy_attachments": 2.3217858620000698, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": 2.2389357920000066, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_update_inline_policy": 4.3136934799999835, - "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_updating_stack_with_iam_role": 12.2537276500002, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Arn]": 0.0017197879999457655, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainArn]": 0.0018652179999207874, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainEndpoint]": 0.0017383230000405092, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainName]": 0.0019911520000732708, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[EngineVersion]": 0.0017127250000612548, - "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Id]": 0.00185815499992259, - "tests/aws/services/cloudformation/resource_providers/scheduler/test_scheduler.py::test_schedule_and_group": 2.5069942829999263, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestBasicCRD::test_black_box": 0.0019623789999059227, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestUpdates::test_update_without_replacement": 0.00173338300010073, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[AllowedPattern]": 0.0017379710000113846, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[DataType]": 0.0021044120001079136, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Description]": 0.0017529989999047757, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Id]": 0.00173100900008194, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Name]": 0.001744584000107352, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Policies]": 0.0018639959999973144, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Tier]": 0.0018595669999967868, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Type]": 0.001763107000101627, - "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Value]": 0.0018585960000336854, - "tests/aws/services/cloudformation/resources/test_acm.py::test_cfn_acm_certificate": 2.1059164670000428, - "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": 12.482138873000167, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_account": 2.1592886179998914, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": 2.103251110000201, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_deployment_canary_settings": 2.267678538000382, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_aws_integration": 2.313341052999931, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_rest_api": 2.302244576999783, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_swagger_import": 2.337293956000053, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": 2.4577514380000594, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": 2.224763251000013, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": 2.3101943039999924, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_with_apigateway_resources": 2.330936880999843, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": 9.927983771000072, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_apigateway_stage": 4.534977057999868, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_usage_plan": 4.489791332000095, - "tests/aws/services/cloudformation/resources/test_apigateway.py::test_url_output": 2.190355274000126, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": 8.627077247000216, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": 8.630928530999881, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": 8.625416834000134, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy": 5.578553291000162, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": 2.421755175000044, - "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_create_macro": 3.1953357089998917, - "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_waitcondition": 2.1993207080001866, - "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_creation": 2.0897398980000617, - "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_ext_statistic": 2.1222695100000237, - "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_composite_alarm_creation": 2.405553236999822, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": 2.4892677540001387, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": 2.4700862259999212, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_default_name_for_table": 2.465942312999914, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_deploy_stack_with_dynamodb_table": 2.2295931429998745, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table": 2.4853841100002683, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": 2.1640082839999195, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_globalindex_read_write_provisioned_throughput_dynamodb_table": 2.191001258999904, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_table_with_ttl_and_sse": 3.1985211460000755, - "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_ttl_cdk": 1.2688372700001764, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_update_ec2_instance_type": 0.00187902200013923, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": 2.4738112479999472, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_tables": 2.2053941829997257, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_dhcp_options": 2.3158978589999606, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": 2.1518465390001893, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_internet_gateway_ref_and_attr": 2.302600224000116, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_keypair_create_import": 2.230479413000012, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation": 2.226517416999968, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": 2.238625385999967, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_transit_gateway_attachment": 2.7630626599998322, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_creates_default_sg": 2.4018880449998505, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_gateway_attachment": 2.1379214569999476, - "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_with_route_table": 2.303093910999678, - "tests/aws/services/cloudformation/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": 4.3315167029998065, - "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_api_destination_resource": 16.397596281999768, - "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_bus_resource": 2.1495795789999192, - "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_creation_without_target": 2.1131767039998977, - "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_to_logs": 2.224708107000197, - "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policies": 17.401304322999977, - "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policy_statement": 2.112093502999869, - "tests/aws/services/cloudformation/resources/test_events.py::test_rule_pattern_transformation": 2.132750053000109, - "tests/aws/services/cloudformation/resources/test_events.py::test_rule_properties": 2.13683564400003, - "tests/aws/services/cloudformation/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": 47.62095491299988, - "tests/aws/services/cloudformation/resources/test_integration.py::test_events_sqs_sns_lambda": 13.586933926000029, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": 11.369442764999803, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_default_parameters_kinesis": 11.30832157000009, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_describe_template": 0.12994928699981756, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": 11.327898677000121, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_kinesis_stream_consumer_creations": 17.303791470000306, - "tests/aws/services/cloudformation/resources/test_kinesis.py::test_stream_creation": 11.320347813000126, - "tests/aws/services/cloudformation/resources/test_kms.py::test_cfn_with_kms_resources": 2.1356382910000775, - "tests/aws/services/cloudformation/resources/test_kms.py::test_deploy_stack_with_kms": 2.117496971999799, - "tests/aws/services/cloudformation/resources/test_kms.py::test_kms_key_disabled": 2.1218810000000303, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": 19.680244657999992, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": 10.827615718000061, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": 21.354543752999916, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": 8.981432404000088, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": 10.16247742199971, - "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": 9.441064245999996, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_cfn_function_url": 7.513547206999874, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_event_invoke_config": 6.264741356999821, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_alias": 12.515562715999977, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": 11.059226107999848, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run": 6.586963471000217, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_empty_string_replacement_deny_list": 6.183029857000065, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_non_empty_string_replacement_deny_list": 6.178779738000003, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_code_signing_config": 2.2008332679999967, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_function_tags": 6.554103537000174, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_layer_crud": 6.278242409999848, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_logging_config": 6.215340495000191, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version": 6.7800975229997675, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": 12.583498315000043, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_vpc": 0.0018956139997499122, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter": 11.465043421000246, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": 12.677869345999852, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": 6.203581426000028, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": 6.66061687499996, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function": 8.294828668999799, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function_name": 12.31021471899976, - "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_permissions": 8.292743442999836, - "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": 2.403029232000108, - "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": 2.120139327000061, - "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain": 0.0018755759999748989, - "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain_with_alternative_types": 23.32355214900008, - "tests/aws/services/cloudformation/resources/test_redshift.py::test_redshift_cluster": 2.1288921210002627, - "tests/aws/services/cloudformation/resources/test_resource_groups.py::test_group_defaults": 2.2588547039999867, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_health_check": 2.2671478540000862, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_id": 2.1964495950001037, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_name": 2.1817148990000987, - "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_without_resource_record": 2.2082141890000457, - "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_autoname": 2.111015418000079, - "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_versioning": 2.1123152459997527, - "tests/aws/services/cloudformation/resources/test_s3.py::test_bucketpolicy": 13.374253075000297, - "tests/aws/services/cloudformation/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": 2.178857264000044, - "tests/aws/services/cloudformation/resources/test_s3.py::test_cors_configuration": 2.5209782189999714, - "tests/aws/services/cloudformation/resources/test_s3.py::test_object_lock_configuration": 2.5251037779999024, - "tests/aws/services/cloudformation/resources/test_s3.py::test_website_configuration": 2.49390838599993, - "tests/aws/services/cloudformation/resources/test_sam.py::test_cfn_handle_serverless_api_resource": 6.663512606000268, - "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_policies": 6.32077437199996, - "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_sqs_event": 13.480790282000044, - "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_template": 6.645215348999955, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": 1.267728618999854, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": 2.2804488829999627, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": 2.1140660279997974, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": 2.130411878999894, - "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": 2.290508434000003, - "tests/aws/services/cloudformation/resources/test_sns.py::test_deploy_stack_with_sns_topic": 2.1459339160001036, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription": 2.1220837299997584, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": 2.162732401999847, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": 2.3450272120001046, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": 2.084964619999937, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_policy_resets_to_default": 1.3596369259998937, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_attributes": 4.473850632999756, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_name": 4.481676719000234, - "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_with_attributes": 1.2338302470000144, - "tests/aws/services/cloudformation/resources/test_sns.py::test_update_subscription": 4.258447144000229, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_cfn_handle_sqs_resource": 2.138399326999888, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": 2.113531048000141, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": 2.1050801760002287, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_queue_policy": 2.1255882590000965, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": 4.2075726730004135, - "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": 4.21962277800003, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_deploy_patch_baseline": 2.263727585000197, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_maintenance_window": 2.1717478059997575, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_parameter_defaults": 2.292983139999933, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameter_tag": 4.200318548000041, - "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameters": 4.1919734269999935, - "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": 1.13675203899993, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke": 9.590245308999556, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost": 9.587556847000087, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost_with_path": 15.669021113000099, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_with_path": 15.649739121000266, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": 4.811609780999788, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_with_dependencies": 2.2020535130004646, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_nested_statemachine_with_sync2": 16.28791141900001, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_retry_and_catch": 0.002642612000045119, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": 2.6762887380002667, - "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_definitionsubstitution": 7.320844774999841, - "tests/aws/services/cloudformation/test_cloudformation_ui.py::TestCloudFormationUi::test_get_cloudformation_ui": 0.07466221900017445, - "tests/aws/services/cloudformation/test_cloudtrail_trace.py::test_cloudtrail_trace_example": 0.00178827500008083, - "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_cfn_with_exports": 2.1223786860002747, - "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_import_values_across_stacks": 4.208472485000129, - "tests/aws/services/cloudformation/test_template_engine.py::TestImports::test_stack_imports": 4.239455152000119, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-0-False]": 0.08758152999985214, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-1-False]": 0.08470163400011188, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-0-False]": 0.08511643199994978, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-1-True]": 2.125580882000122, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-0-False]": 0.08687116500004777, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-1-True]": 2.1220813180000277, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-0-True]": 2.1202731069997753, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-1-True]": 2.119773032000012, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_base64_sub_and_getatt_functions": 2.125412026999811, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": 2.102692367000145, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cidr_function": 0.0017986640000344778, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_find_map_function": 2.104354831999899, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": 2.111459384999989, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": 2.1139315299999453, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": 2.1145585579997714, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": 2.1171907700002066, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": 2.107781692999879, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": 2.116655185000127, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": 2.1327635830000418, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": 2.133815008000056, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": 2.1120683500002997, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_split_length_and_join_functions": 2.189631771000222, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_not_ready": 2.1271988329997384, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": 2.1033954980000544, - "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_to_json_functions": 0.002010878999954002, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_attribute_uses_macro": 5.751275661000136, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_capabilities_requirements": 5.315533566999875, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": 0.026388910999685322, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": 3.6874610350000694, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": 3.671991045999903, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": 3.668150611000101, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": 3.7413466499999686, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": 4.7072309990003305, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_global_scope": 5.108609515000126, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_macro_deployment": 3.2180161139999655, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_pyplate_param_type_list": 8.731333165000024, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_scope_order_and_parameters": 0.001988356999845564, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": 5.736256714000092, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": 5.7333500339996135, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": 3.751257054000007, - "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_validate_lambda_internals": 5.232508127999836, - "tests/aws/services/cloudformation/test_template_engine.py::TestPreviousValues::test_parameter_usepreviousvalue_behavior": 0.0018421350000608072, - "tests/aws/services/cloudformation/test_template_engine.py::TestPseudoParameters::test_stack_id": 2.111392901999807, - "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager.yaml]": 2.1087372459999187, - "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_full.yaml]": 2.121885125999597, - "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_partial.yaml]": 2.111090620999903, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": 2.175875404000408, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": 2.167477854999788, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm": 2.128136857999607, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_secure": 2.1288022129999717, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_with_version": 2.1541232830002173, - "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": 6.248860372000308, - "tests/aws/services/cloudformation/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": 2.3775358590000906, - "tests/aws/services/cloudformation/test_template_engine.py::TestTypes::test_implicit_type_conversion": 2.1645039720001478, - "tests/aws/services/cloudformation/test_unsupported.py::test_unsupported": 2.1014740190000794, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": 1.6277746300002036, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle": 0.0018464029997176112, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created": 2.4014476829997875, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions": 5.322847334000016, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream": 0.0017945750000762928, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle": 0.14175453999996535, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering": 0.11493404499969984, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm": 0.08970813999985694, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly": 0.07479502000001048, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm": 0.07912031099999695, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions": 10.271388912999555, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data": 2.066651912999987, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data0]": 0.0018501280005693843, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data1]": 0.0018933789997390704, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data2]": 0.0019816939998236194, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics": 1.058759911000152, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination": 2.1879815400000098, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Average]": 0.032803770999862536, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Maximum]": 0.03470120599990878, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Minimum]": 0.034163922000061575, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[SampleCount]": 0.033606618000249, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Sum]": 0.03436501800001679, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units": 0.026715442999829975, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions": 0.041627914999935456, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels": 0.03881419300023481, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics": 0.17638965900005132, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results": 0.05586317000006602, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions": 0.03129234899961375, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units": 0.029274437999902148, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule": 0.0017910289998326334, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints": 0.5417431649998434, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name": 0.017128610000099798, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs0]": 0.03330601400057276, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs1]": 0.03427305000013803, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs2]": 0.03273343899991232, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs3]": 0.031359334999706334, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs4]": 0.039084312999420945, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs5]": 0.0331920419994276, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs6]": 0.03170272300030774, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination": 5.267604198999834, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness": 2.058439638000209, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters": 4.0895958910000445, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget": 0.0017079959998227423, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions": 2.1080690960000084, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics": 0.05541996100009783, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_parallel_put_metric_data_list_metrics": 0.25255249399970126, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms": 0.09183584800007338, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm": 10.669943869000008, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character": 0.07046896500014554, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_gzip": 0.02405898500001058, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation": 0.03998096599980272, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list": 0.032191664000038145, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc": 0.029245805999835284, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_raw_metric_data": 0.024513160000196876, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm": 2.31722782199995, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input": 0.08571427100014262, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags": 0.1186952709997513, - "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm": 4.657030024000051, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_error": 2.537420996000492, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_successful": 2.502628583999922, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSQSMetrics::test_alarm_number_of_messages_sent": 62.49524874599956, - "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSqsApproximateMetrics::test_sqs_approximate_metrics": 33.663942856000176, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_binary": 0.09680220699965503, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items": 0.09802335899985337, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items_streaming": 1.144631229999959, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_existing_table": 0.1876054320005096, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_matching_schema": 0.11272261500016612, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_binary_data_with_stream": 0.7822834629996578, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_continuous_backup_update": 0.27734773299971494, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_create_duplicate_table": 0.11117041199986488, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_data_encoding_consistency": 0.9171882570008165, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_delete_table": 0.11131214199986061, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_batch_execute_statement": 0.1797189369995067, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_class": 0.15128172100003212, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_partial_sse_specification": 0.1173242609997942, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_sse_specification": 0.07883276199981992, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_statement_empy_parameter": 0.12224206100017909, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_transaction": 0.27820700300026147, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_get_batch_items": 0.08045227599950522, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_idempotent_writing": 0.15584822800019538, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_partiql_missing": 0.12022950999971727, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_pay_per_request": 0.039874329999747715, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_records_with_update_item": 0.002012940999975399, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_shard_iterator": 0.8347537959994042, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_stream_view_type": 1.340803656999924, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_describe_with_exclusive_start_shard_id": 0.7963936379996994, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_shard_iterator_format": 2.859337337999932, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_update_table_without_sse_specification_change": 0.10459768900000199, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_with_kinesis_stream": 1.4560579110002436, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_empty_and_binary_values": 0.0963349929998003, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables": 0.09859411300021748, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables_version_2019": 0.4566281880001952, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PAY_PER_REQUEST]": 0.3433099939998101, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PROVISIONED]": 0.31824802200071645, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_invalid_query_index": 0.06909960899974976, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_large_data_download": 0.3462543749997167, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_list_tags_of_resource": 0.07618844399985392, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_more_than_20_global_secondary_indexes": 0.273366521000753, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_multiple_update_expressions": 0.1524250530005702, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_non_ascii_chars": 0.138322091000191, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_nosql_workbench_localhost_region": 0.09279170900026656, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_query_on_deleted_resource": 0.1368677509999543, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_in_put_item": 0.12583083600020473, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_on_conditions_check_failure": 0.22017002999973556, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_stream_destination_records": 11.875456876000044, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_streams_on_global_tables": 1.208146169000429, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live": 0.23198407799964116, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live_deletion": 0.40941584399979547, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_get_items": 0.111099947999719, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming": 1.3158019369998328, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming_for_different_tables": 1.2081446120000692, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_binary_data": 0.09446526300007463, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_canceled": 0.10906978100001652, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_items": 0.12160609000011391, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_local_secondary_index": 0.12473020400057067, - "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_query_index": 0.07548662699946362, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_enable_kinesis_streaming_destination": 0.0018246619997626112, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_non_existent_stream": 0.01571254800001043, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_stream_spec_and_region_replacement": 2.303734611999971, - "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_table_v2_stream": 1.5392282500001784, - "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3": 0.6366650779996235, - "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3_validation": 0.20112776200039661, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_route_table_association": 0.2311961559998963, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-id_manager]": 0.06505460500011395, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-tag]": 0.06486644899996463, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-id_manager]": 0.054846220000399626, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-tag]": 0.0561807150002096, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id": 0.05512303800014706, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id_and_vpc_id": 0.0634535379999761, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_tags": 0.047370952000164834, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_endpoint": 0.1547316009991846, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_with_custom_id": 0.0448797970002488, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpc_endpoints_with_filter": 0.41373035600008734, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpn_gateways_filter_by_vpc": 0.48013521899974876, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_get_security_groups_for_vpc": 0.3312256979997983, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[id]": 0.0683017760002258, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[name]": 0.053923260999454214, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_reserved_instance_api": 0.03877268599990202, - "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vcp_peering_difference_regions": 1.2825011100003394, - "tests/aws/services/ec2/test_ec2.py::test_create_specific_vpc_id": 0.03137461100004657, - "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_ids": 0.30762627700005396, - "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_names": 0.30975895899973693, - "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filters": 0.3112914960006492, - "tests/aws/services/ec2/test_ec2.py::test_pickle_ec2_backend": 6.852697470000294, - "tests/aws/services/ec2/test_ec2.py::test_raise_create_volume_without_size": 0.021355883000069298, - "tests/aws/services/ec2/test_ec2.py::test_raise_duplicate_launch_template_name": 0.0364137300002767, - "tests/aws/services/ec2/test_ec2.py::test_raise_invalid_launch_template_name": 0.01627439100002448, - "tests/aws/services/ec2/test_ec2.py::test_raise_modify_to_invalid_default_version": 0.03491432499959046, - "tests/aws/services/ec2/test_ec2.py::test_raise_when_launch_template_data_missing": 0.012592912999934924, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_domain": 0.0018040730001303018, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_existing_domain_causes_exception": 0.001716611000119883, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_describe_domains": 0.0018779699998958677, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_domain_version": 0.0017387520001648227, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_version_for_domain": 0.0017975619998651382, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_versions": 0.0018184799996561196, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_list_versions": 0.0017854689995147055, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_path_endpoint_strategy": 0.0017444729996896058, - "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_update_domain_config": 0.0017370890000165673, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth0]": 0.10480022099955022, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth1]": 0.09125146099995618, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth2]": 0.09104526599958263, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_invalid_parameters": 0.015226594999603549, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_name_validation": 0.04467158500028745, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[api-key]": 0.053870610000103625, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[basic]": 0.05409580699961225, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[oauth]": 0.05951326700005666, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection": 0.048518672999307455, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_invalid_parameters": 0.014883462999932817, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_name_validation": 0.01544298299950242, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params0]": 0.05037477099995158, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params1]": 0.04921408000018346, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params2]": 0.049713293999957386, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_delete_connection": 0.08691886499946122, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_list_connections": 0.04771938100020634, - "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_update_connection": 0.08877445100006298, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[custom]": 0.087992665999991, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[default]": 0.05878885999982231, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_unknown_event_bus": 0.01737252399971112, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[custom]": 0.11249871199970585, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[default]": 0.09121549999963463, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_delete_archive_error_unknown_archive": 0.014323886000056518, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_describe_archive_error_unknown_archive": 0.015995275000022957, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_error_unknown_source_arn": 0.014252464000037435, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[custom]": 0.0845472940000036, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[default]": 0.05786559599937391, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-custom]": 0.5592493879998983, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-default]": 0.5191726449997986, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-custom]": 0.5479330949997347, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-default]": 0.5277801860002, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[custom]": 0.10885213399978966, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[default]": 0.07370551900021383, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[custom]": 0.08804015299983803, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[default]": 0.06308690099967862, - "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_update_archive_error_unknown_archive": 0.0018776409997371957, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_describe_replay_error_unknown_replay": 0.0191208840001309, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replay_with_limit": 0.22034868199943958, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_event_source_arn": 0.10116432999984681, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_prefix": 0.1513348609996683, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[custom]": 0.0018572019998828182, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[default]": 0.0018708380002863123, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_different_archive": 0.12461842799984879, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_name_same_archive": 0.07439145899979849, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[0]": 0.06521697900006984, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[10]": 0.06573443399929602, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_archive": 0.015453206000074715, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_event_bus": 0.09268174299995735, - "tests/aws/services/events/test_archive_and_replay.py::TestReplay::tests_concurrency_error_too_many_active_replays": 0.0018657470004654897, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions0]": 0.5916570119999847, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions1]": 0.1499591520000365, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions0]": 0.04279285600023286, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions1]": 0.5986392120003075, - "tests/aws/services/events/test_events.py::TestEventBus::test_create_multiple_event_buses_same_name": 0.05493102699998076, - "tests/aws/services/events/test_events.py::TestEventBus::test_delete_default_event_bus": 0.01661198799996555, - "tests/aws/services/events/test_events.py::TestEventBus::test_describe_delete_not_existing_event_bus": 0.02512090700000158, - "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_limit": 0.2634509559999856, - "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_prefix": 0.08737263200001166, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[domain]": 0.3304765190000012, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[path]": 0.3385476519999884, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[standard]": 1.5556356740000012, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_nonexistent_event_bus": 0.1811130600000297, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_to_default_eventbus_for_custom_eventbus": 1.4805967420000172, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[custom]": 0.3920678670000086, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[default]": 0.10752020200001766, - "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission_non_existing_event_bus": 0.01836778499995262, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[custom]": 0.09767507399999431, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[default]": 0.08137245800003257, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-custom]": 0.046209368000006634, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-default]": 0.024307401000044138, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-custom]": 0.05907034599999861, - "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-default]": 0.034541739999951915, - "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_nested": 10.312040144999969, - "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_with_values_in_array": 5.3301383360000045, - "tests/aws/services/events/test_events.py::TestEventRule::test_delete_rule_with_targets": 0.08717620499999157, - "tests/aws/services/events/test_events.py::TestEventRule::test_describe_nonexistent_rule": 0.03325594099999307, - "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[custom]": 0.10844446999999491, - "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[default]": 0.07897800099999586, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[custom]": 0.26362091700002566, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[default]": 0.23942418000001453, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[custom]": 0.1863909659999763, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[default]": 0.12855386500001487, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[custom]": 0.5337221049999812, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[default]": 0.3971814210000275, - "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_with_limit": 0.25721301800001584, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_pattern_to_single_matching_rules_single_target": 7.468139645000008, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_different_targets": 0.57108457399994, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_single_target": 18.918436059999976, - "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_single_matching_rules_single_target": 10.806478302000073, - "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[custom]": 0.10241549399998462, - "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[default]": 0.07642783400001463, - "tests/aws/services/events/test_events.py::TestEventRule::test_put_multiple_rules_with_same_name": 0.09920640399997183, - "tests/aws/services/events/test_events.py::TestEventRule::test_update_rule_with_targets": 0.13301933900001472, - "tests/aws/services/events/test_events.py::TestEventTarget::test_add_exceed_fife_targets_per_rule": 0.15165980699993042, - "tests/aws/services/events/test_events.py::TestEventTarget::test_list_target_by_rule_limit": 0.1700677939999764, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[custom]": 0.18590434099996855, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[default]": 0.13350425700008373, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_across_different_rules": 0.11364998299995932, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_single_rule": 0.07832990699995435, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_across_different_rules": 0.11950475800000504, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_single_rule": 0.7746673739999892, - "tests/aws/services/events/test_events.py::TestEventTarget::test_put_target_id_validation": 0.12683641500001386, - "tests/aws/services/events/test_events.py::TestEvents::test_create_connection_validations": 0.015873895999902743, - "tests/aws/services/events/test_events.py::TestEvents::test_events_written_to_disk_are_timestamp_prefixed_for_chronological_ordering": 0.0019057520003116224, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[ARRAY]": 0.014473945000190724, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[MALFORMED_JSON]": 0.014726731000337168, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[SERIALIZED_STRING]": 0.014474444999905245, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[STRING]": 0.014344474999688828, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_with_too_big_detail": 0.021365156000683783, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail": 0.014007481000589905, - "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail_type": 0.015228257000217127, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[custom]": 0.05290659699994649, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[default]": 0.020045391000167, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_response_entries_order": 0.2942123359998732, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_time": 0.3135512449998714, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_target_delivery_failure": 1.1585662629995568, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_time_field": 0.18708987499985597, - "tests/aws/services/events/test_events.py::TestEvents::test_put_events_without_source": 0.0144448579994787, - "tests/aws/services/events/test_events_cross_account_region.py::TestEventsCrossAccountRegion::test_put_events[custom-account]": 0.15858059400005686, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-account]": 0.4466854919999719, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region]": 0.4486309449999908, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region_account]": 0.518361055000014, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-account]": 0.4961188409999977, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region]": 0.47757019599998785, - "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region_account]": 0.4880931999999234, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path": 0.18621358000001464, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_max_level_depth": 0.19015718499997547, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_multiple_targets": 0.30344321100000116, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail0]": 0.18428279200003317, - "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail1]": 0.19212181500000725, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" multiple list items\"]": 0.2268938079999998, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item multiple list items system account id payload user id\"]": 0.23086507200008555, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item\"]": 0.23133850899995423, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Payload of with path users-service/users/ and \"]": 0.2451491880000276, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : \"\"}]": 0.235893754000017, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : }]": 0.23637139399994567, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"nested\": {\"level1\": {\"level2\": {\"level3\": \"users-service/users/\"} } }, \"bod\": \"\"}]": 0.23232791000003772, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": }]": 0.24720958199998222, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": [, \"hardcoded\"]}]": 0.23662398400000484, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"id\": , \"body\": }]": 0.24194572099997913, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"multi_replacement\": \"users//second/\"}]": 0.22862355199998774, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"singlelistitem\": }]": 0.22901070699998627, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"not_valid\": \"users-service/users/\", \"bod\": }]": 5.152465397000071, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"payload\": \"\"}]": 5.152921901000013, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"singlelistitem\": \"\"}]": 5.154503601999977, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[\"Message containing all pre defined variables \"]": 0.2378380920000609, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[{\"originalEvent\": , \"originalEventJson\": }]": 0.24159446400000206, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_json": 0.43309108800002605, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"Event of type, at time , info extracted from detail \"]": 0.40682580000003554, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"{[/Check with special starting characters for event of type\"]": 0.4033483890000298, - "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_missing_keys": 0.12469054600001073, - "tests/aws/services/events/test_events_inputs.py::test_put_event_input_path_and_input_transformer": 0.10073224499996059, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_array_event_payload": 0.013761892000047737, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays]": 0.013869510000006358, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_NEG]": 0.01611027199999171, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_EXC]": 0.08924887000000581, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_null_NEG]": 0.013772399999993468, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean]": 0.01344544500000211, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean_NEG]": 0.013785388000030707, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_many_rules]": 0.014838442000041141, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match]": 0.014221395000049597, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match_NEG]": 0.013326175999964107, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or]": 0.014150890999928833, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or_NEG]": 0.013729022999996232, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase]": 0.013473015999977633, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_EXC]": 0.08877988699998696, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_NEG]": 0.014513938999982656, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list]": 0.013655780000078721, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_EXC]": 0.08987392999995336, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_NEG]": 0.01350532999998677, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number]": 0.01333547400002999, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_NEG]": 0.013791293000053884, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list]": 0.013654863000056139, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list_NEG]": 0.013514342999997098, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_zero]": 0.013532857999962289, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string]": 0.014042335999988609, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_NEG]": 0.013493593000021065, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list]": 0.013743070000032276, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_NEG]": 0.014037629999904766, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null]": 0.013151420000042435, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix]": 0.014301217000024735, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_NEG]": 0.013559527000040816, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_empty_EXC]": 0.08891592199995557, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_ignorecase_EXC]": 0.09270923800005448, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_EXC]": 0.08801470099996322, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list]": 0.013490899999965222, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_NEG]": 0.013492892999920514, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_type_EXC]": 0.08859355399994229, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix]": 0.013676859999975477, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_NEG]": 0.013558800999987852, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_empty_EXC]": 0.08839110200000277, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_ignorecase_EXC]": 0.08862313899999208, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_int_EXC]": 0.08884700099997644, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list]": 0.013794718000042394, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_NEG]": 0.013654609000013807, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_type_EXC]": 0.0885851700000444, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard]": 0.015080563000083202, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_NEG]": 0.014160532000005333, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_empty]": 0.014345531999993, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list]": 0.01386612599998216, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_NEG]": 0.013645905000089442, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_type_EXC]": 0.08842374199997494, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_type_EXC]": 0.08974990300004038, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists]": 0.013451749000068958, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_NEG]": 0.013785020000057102, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false]": 0.013979776999974547, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false_NEG]": 0.014494933000037236, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase]": 0.013585698999975193, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_EXC]": 0.0881033889999685, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_NEG]": 0.013679846999991696, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty]": 0.013848548999987997, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty_NEG]": 0.01354730599996401, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_list_EXC]": 0.08791242500007002, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address]": 0.013786460000005718, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_EXC]": 0.08857102299992903, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_NEG]": 0.014364193999995223, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_ip_EXC]": 0.09161852099998669, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_mask_EXC]": 0.08809080300000005, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_type_EXC]": 0.08906296300000349, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6]": 0.014971706999972412, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_NEG]": 0.014618184999960704, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_bad_ip_EXC]": 0.08769140100002915, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_EXC]": 0.08949149399995804, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and]": 0.014097832999993898, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and_NEG]": 0.01351774300002262, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_number_EXC]": 0.08778565700004037, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_operatorcasing_EXC]": 0.08833025900003122, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_syntax_EXC]": 0.08894247400002087, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix]": 0.0133829499999365, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_NEG]": 0.013547810999966714, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_empty]": 0.013741715999969983, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_ignorecase]": 0.013709525000024314, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_int_EXC]": 0.09242304600002171, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_list_EXC]": 0.08745058699997799, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix]": 0.01498193300000139, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_NEG]": 0.015042467999990095, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_empty]": 0.013931108000008408, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase]": 0.013725194999949508, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase_NEG]": 0.01355103800000279, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_int_EXC]": 0.08885789199996452, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_list_EXC]": 0.08888014700005442, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_complex_EXC]": 0.08937593600001037, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_empty_NEG]": 0.01361535000000913, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_int_EXC]": 0.08858862499999987, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_list_EXC]": 0.08795768299995643, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating]": 0.013706912000031934, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating_NEG]": 0.013687930000060078, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating]": 0.01362722100003566, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_NEG]": 0.013901419000035276, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_star_EXC]": 0.08831728399997019, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_simplified]": 0.013499067999987346, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event]": 0.013227012000015748, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event_NEG]": 0.013754148999964855, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern]": 0.014134996000052524, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern_NEG]": 0.013316559999964284, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dynamodb]": 0.014583652999988317, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb]": 0.0151764149999849, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb_NEG]": 0.014429379000034714, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_list_empty_NEG]": 0.01372069200004944, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[int_nolist_EXC]": 0.0889115880000304, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[key_case_sensitive_NEG]": 0.013396763999992345, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[list_within_dict]": 0.014758323999956247, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[minimal]": 0.014121142999954372, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[nested_json_NEG]": 0.013558455999941543, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value]": 0.013435006000008798, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value_NEG]": 0.013979821000077663, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[number_comparison_float]": 0.013669547000006332, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-int-float]": 0.013543515000037587, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-null_NEG]": 0.013652526999976544, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-string_NEG]": 0.013310861000036311, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_case_sensitive_EXC]": 0.08861350899996978, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_multiple_list]": 0.014563270999985889, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-anything-but]": 0.014297489000000496, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists-parent]": 0.01322872800000141, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists]": 0.014073646999975153, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but]": 0.014169268000046031, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but_NEG]": 0.013783275000037065, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[prefix]": 0.013705123000079311, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[sample1]": 0.01360441299999593, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string]": 0.014019844000017656, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_empty]": 0.014206067999907646, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_nolist_EXC]": 0.09041792899995471, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_source": 0.02226104299995768, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_escape_characters": 0.011945944000046893, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_multi_key": 0.011855292999996436, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_with_large_and_complex_payload": 0.025505935000012414, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_event_payload": 0.013939423999943301, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[[\"not\", \"a\", \"dict\", \"but valid json\"]]": 0.08703950000005989, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[this is valid json but not a dict]": 0.0877497109999581, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{\"not\": closed mark\"]": 0.0878949649999754, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{'bad': 'quotation'}]": 0.08769687200009457, - "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_plain_string_payload": 0.014448876999949789, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_event_with_content_base_rule_in_pattern": 0.1909395089999748, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_anything_but": 5.307662837000066, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_false": 5.236193000000014, - "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_true": 5.237757904000034, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::test_schedule_cron_target_sqs": 0.0017857720000051813, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 1 * * * *)]": 0.013375488000008318, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 dummy ? * MON-FRI *)]": 0.01333643100002746, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(7 20 * * NOT *)]": 0.01310403400003679, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(71 8 1 * ? *)]": 0.013144777999912094, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(INVALID)]": 0.012941196999975091, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(* * ? * SAT#3 *)]": 0.036204294999947706, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 10 * * ? *)]": 0.03485380500001156, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 12 * * ? *)]": 0.03652054200000521, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 18 ? * MON-FRI *)]": 0.035413559000005534, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT *)]": 0.03682294200001479, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT#3 *)]": 0.03767543300000398, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 8 1 * ? *)]": 0.035038143999940985, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/10 * ? * MON-FRI *)]": 0.03586612599997352, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/15 * * * ? *)]": 0.036082542999906764, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 0-2 ? * MON-FRI *)]": 0.03600140100002136, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 20-23 ? * MON-FRI *)]": 0.0358866280000143, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 5 ? JAN 1-5 2022)]": 0.03554724299999634, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 8-17 ? * MON-FRI *)]": 0.03664440399995783, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 10 ? * 6L 2002-2005)]": 0.03655931499997678, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 12 * * ? *)]": 0.03514118500004315, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(5,35 14 * * ? *)]": 0.03507266299999401, - "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_scheduled_rule_does_not_trigger_on_put_events": 3.093507466999995, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[ rate(10 minutes)]": 0.011178101000041352, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate( 10 minutes )]": 0.01102510200001916, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate()]": 0.01120636699994293, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(-10 minutes)]": 0.010865946000023996, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(0 minutes)]": 0.01188895100000309, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 days)]": 0.011542329999997492, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 hours)]": 0.011523239999974066, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 minutes)]": 0.01138427099999717, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 MINUTES)]": 0.01133924400005526, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 day)]": 0.01096904199994242, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 hour)]": 0.011461326000073768, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minute)]": 0.011292459000003419, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minutess)]": 0.011242023000022527, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 seconds)]": 0.011806366000030266, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 years)]": 0.011099297000043862, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10)]": 0.010788887000046543, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(foo minutes)]": 0.010758441999996649, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_schedule_rate": 0.03661629000004041, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_scheduled_rule_logs": 0.0017338850000214734, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_put_rule_with_schedule_custom_event_bus": 0.04097579500006532, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_custom_input_target_sqs": 60.113167524000005, - "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_target_sqs": 0.0017681480000533156, - "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_create_event_bus_with_tags": 0.04279605499999661, - "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_list_tags_for_deleted_event_bus": 0.03508793200001037, - "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_list_tags_for_deleted_rule": 0.0632267949999914, - "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_put_rule_with_tags": 0.06351764899994805, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_custom]": 0.0664305390000095, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_default]": 0.019215511000027163, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_custom]": 0.08666830199996411, - "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_default]": 0.06220379299998058, - "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_event_bus]": 0.02785836000003883, - "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_rule]": 0.029378054000062548, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_custom]": 0.06752063999999791, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_default]": 0.045605253000019275, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_custom]": 0.08998515000001817, - "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_default]": 0.06203210600000375, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth0]": 0.14323040100003936, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth1]": 0.10881754299992963, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth2]": 0.11049693100005697, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiGateway::test_put_events_with_target_api_gateway": 8.710607390999996, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetCloudWatchLogs::test_put_events_with_target_cloudwatch_logs": 0.20238146999997753, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination0]": 0.28791984700001194, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination1]": 0.3107105540000248, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination2]": 0.2815656139999305, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetFirehose::test_put_events_with_target_firehose": 0.9962418499999899, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetKinesis::test_put_events_with_target_kinesis": 2.293001970999967, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda": 4.2530116890000045, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entries_partial_match": 4.260001307999971, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entry": 4.265917921000039, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[domain]": 0.21793921500005808, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[path]": 0.21901189800007614, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[standard]": 0.4945042570000169, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs": 0.18155888699999423, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs_event_detail_match": 5.213270178000016, - "tests/aws/services/events/test_events_targets.py::TestEventsTargetStepFunctions::test_put_events_with_target_statefunction_machine": 4.248273761000064, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_api_gateway": 5.734778914000003, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination0]": 4.370722668999974, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination1]": 4.394832898000004, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination2]": 4.368500753000035, - "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_lambda": 4.245715679, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_elasticsearch_s3_backup": 0.0018748919999893587, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source": 32.265390741999965, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source_multiple_delivery_streams": 47.464969440000004, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[domain]": 0.0018301770000448414, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[path]": 0.0017735010000023976, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[port]": 0.0017991089999895848, - "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_s3_as_destination_with_file_extension": 1.17137940300006, - "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[False]": 0.07366622000006373, - "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[True]": 1.5691289929999925, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_role_with_malformed_assume_role_policy_document": 0.01875377200008188, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_add_permission_boundary_afterwards": 0.13109303300007014, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_with_permission_boundary": 0.11048760999995011, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_role": 0.12425079100000858, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_root": 0.04324465600006988, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_user": 0.17713360099992315, - "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_role_with_path_lifecycle": 0.11569229499991707, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_detach_role_policy": 0.07980753499987259, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_iam_role_to_new_iam_user": 0.09270491600000241, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_describe_role": 0.14249356099992383, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_role_with_assume_role_policy": 0.12463319800008321, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_user_with_tags": 0.03165785399994547, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_delete_non_existent_policy_returns_no_such_entity": 0.015146510000022317, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_instance_profile_tags": 0.14189170999998169, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_list_roles_with_permission_boundary": 0.1681864999999334, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_recreate_iam_role": 0.057760691000112274, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_role_attach_policy": 0.40244757800007847, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[ecs.amazonaws.com-AWSServiceRoleForECS]": 0.002102631999946425, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[eks.amazonaws.com-AWSServiceRoleForAmazonEKS]": 0.0017579049999767449, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[group]": 0.18749593599989112, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[role]": 0.21284335299992563, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[user]": 0.21774207400017076, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_update_assume_role_policy": 0.11208550199989986, - "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_user_attach_policy": 0.41609259899996687, - "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_group_policy_encoding": 0.05359751800006052, - "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_role_policy_encoding": 0.17183776700005637, - "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_user_policy_encoding": 0.07715919699990081, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_already_exists": 0.031646423999973194, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_deletion": 7.593887749000032, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[accountdiscovery.ssm.amazonaws.com]": 0.2752406140000403, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[acm.amazonaws.com]": 0.2752985529999705, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[appmesh.amazonaws.com]": 0.2763414930000181, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling-plans.amazonaws.com]": 0.274928994999982, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling.amazonaws.com]": 0.2782941870000286, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[backup.amazonaws.com]": 0.27918300700002874, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[batch.amazonaws.com]": 0.2767989729999272, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cassandra.application-autoscaling.amazonaws.com]": 0.2758316050000076, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cks.kms.amazonaws.com]": 0.27402096399987386, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cloudtrail.amazonaws.com]": 0.2720094420000123, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[codestar-notifications.amazonaws.com]": 0.27517953799997485, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[config.amazonaws.com]": 0.27831981100007397, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[connect.amazonaws.com]": 0.2728893280000193, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms-fleet-advisor.amazonaws.com]": 0.2759268919999158, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms.amazonaws.com]": 1.0149976540001262, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[docdb-elastic.amazonaws.com]": 0.2757492960000718, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2-instance-connect.amazonaws.com]": 0.27317138800003704, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2.application-autoscaling.amazonaws.com]": 0.276871114999949, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecr.amazonaws.com]": 0.2770268389999728, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecs.amazonaws.com]": 0.27507390799996756, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-connector.amazonaws.com]": 0.28776161799987676, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-fargate.amazonaws.com]": 0.31884082000010494, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-nodegroup.amazonaws.com]": 0.2763008860000582, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks.amazonaws.com]": 0.2752560649998941, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticache.amazonaws.com]": 0.27847521199998937, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticbeanstalk.amazonaws.com]": 0.27523259500014774, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticfilesystem.amazonaws.com]": 0.27523129100006827, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticloadbalancing.amazonaws.com]": 0.2742660799999612, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[email.cognito-idp.amazonaws.com]": 0.284148423000147, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emr-containers.amazonaws.com]": 0.2743374279999671, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emrwal.amazonaws.com]": 0.2743696610001507, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[fis.amazonaws.com]": 0.2763241470000821, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[grafana.amazonaws.com]": 0.27593888100000186, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[imagebuilder.amazonaws.com]": 0.2755994039999905, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[iotmanagedintegrations.amazonaws.com]": 0.3501174450000235, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafka.amazonaws.com]": 0.27690836700003274, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafkaconnect.amazonaws.com]": 0.27679552500001137, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lakeformation.amazonaws.com]": 0.27673467400018126, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lex.amazonaws.com]": 0.34858265500008656, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lexv2.amazonaws.com]": 0.27458409699988806, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lightsail.amazonaws.com]": 0.27451274599991393, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[m2.amazonaws.com]": 0.2758543170000394, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[memorydb.amazonaws.com]": 0.28122698300012416, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mq.amazonaws.com]": 0.2735537959999874, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mrk.kms.amazonaws.com]": 0.2787365519999412, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[notifications.amazonaws.com]": 0.27819534699995074, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[observability.aoss.amazonaws.com]": 0.2743628839999701, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opensearchservice.amazonaws.com]": 0.2769217730000264, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.apigateway.amazonaws.com]": 0.2738951049998377, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.emr-serverless.amazonaws.com]": 0.2773627310000393, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsdatasync.ssm.amazonaws.com]": 0.2741581259999748, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsinsights.ssm.amazonaws.com]": 0.2742423230000668, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[pullthroughcache.ecr.amazonaws.com]": 0.27309717999980876, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ram.amazonaws.com]": 0.2726664389998632, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rds.amazonaws.com]": 0.2746395900001062, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[redshift.amazonaws.com]": 0.27554116500004966, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.cassandra.amazonaws.com]": 0.2731416009999066, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.ecr.amazonaws.com]": 0.28532521799991173, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[repository.sync.codeconnections.amazonaws.com]": 0.3000617369999645, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[resource-explorer-2.amazonaws.com]": 0.275849052000126, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rolesanywhere.amazonaws.com]": 0.28825108200010163, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[s3-outposts.amazonaws.com]": 0.27488815399999567, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ses.amazonaws.com]": 0.27424611100002494, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[shield.amazonaws.com]": 1.068050421999942, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-incidents.amazonaws.com]": 0.27259832200002165, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-quicksetup.amazonaws.com]": 0.2743299879998631, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm.amazonaws.com]": 0.27460381500009134, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[sso.amazonaws.com]": 0.2758797330000107, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[vpcorigin.cloudfront.amazonaws.com]": 0.2742634520000138, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[waf.amazonaws.com]": 0.2767856440000287, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[wafv2.amazonaws.com]": 0.2751034420001588, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[autoscaling.amazonaws.com]": 0.1275606910000988, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[connect.amazonaws.com]": 0.1269358279997732, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[lexv2.amazonaws.com]": 0.12870486099996015, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[accountdiscovery.ssm.amazonaws.com]": 0.015229791999900044, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[acm.amazonaws.com]": 0.01504314299984344, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[appmesh.amazonaws.com]": 0.014954947000092034, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[autoscaling-plans.amazonaws.com]": 0.015223011000102815, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[backup.amazonaws.com]": 0.01528208500008077, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[batch.amazonaws.com]": 0.015066897000110657, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cassandra.application-autoscaling.amazonaws.com]": 0.015177265000033913, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cks.kms.amazonaws.com]": 0.015150803999972595, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cloudtrail.amazonaws.com]": 0.015260430000012093, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[codestar-notifications.amazonaws.com]": 0.015147258000069996, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[config.amazonaws.com]": 0.014891979000026367, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms-fleet-advisor.amazonaws.com]": 0.01496775099997194, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms.amazonaws.com]": 0.015181431999963024, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[docdb-elastic.amazonaws.com]": 0.015014247999943109, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2-instance-connect.amazonaws.com]": 0.015291961000116316, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2.application-autoscaling.amazonaws.com]": 0.01522660800014819, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecr.amazonaws.com]": 0.015011251999908382, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecs.amazonaws.com]": 0.015154843000004803, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-connector.amazonaws.com]": 0.015372712999919713, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-fargate.amazonaws.com]": 0.015409983000040484, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-nodegroup.amazonaws.com]": 0.014966518999926848, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks.amazonaws.com]": 0.015263064999999187, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticache.amazonaws.com]": 0.01519897499997569, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticbeanstalk.amazonaws.com]": 0.015082356000107211, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticfilesystem.amazonaws.com]": 0.014992598000048929, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticloadbalancing.amazonaws.com]": 0.015179909000039515, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[email.cognito-idp.amazonaws.com]": 0.017178508999904807, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emr-containers.amazonaws.com]": 0.015066237000041838, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emrwal.amazonaws.com]": 0.01539340299984815, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[fis.amazonaws.com]": 0.015364478000037707, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[grafana.amazonaws.com]": 0.014938760000063667, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[imagebuilder.amazonaws.com]": 0.015780089000031694, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[iotmanagedintegrations.amazonaws.com]": 0.014987139000027128, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafka.amazonaws.com]": 0.015072880000047917, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafkaconnect.amazonaws.com]": 0.01512045699985265, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lakeformation.amazonaws.com]": 0.016527165000070454, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lex.amazonaws.com]": 0.015147267999964242, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lightsail.amazonaws.com]": 0.015237618000014663, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[m2.amazonaws.com]": 0.015058450000083212, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[memorydb.amazonaws.com]": 0.015071003999992172, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mq.amazonaws.com]": 0.015312269000105516, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mrk.kms.amazonaws.com]": 0.015350128999898516, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[notifications.amazonaws.com]": 0.014879985999982637, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[observability.aoss.amazonaws.com]": 0.015412158000117415, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opensearchservice.amazonaws.com]": 0.01548443199999383, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.apigateway.amazonaws.com]": 0.015113284999983989, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.emr-serverless.amazonaws.com]": 0.014991014999964136, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsdatasync.ssm.amazonaws.com]": 0.014977658999896448, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsinsights.ssm.amazonaws.com]": 0.014981332999923325, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[pullthroughcache.ecr.amazonaws.com]": 0.015175261000081264, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ram.amazonaws.com]": 0.015239312000062455, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rds.amazonaws.com]": 0.015122700999995686, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[redshift.amazonaws.com]": 0.01530842300007862, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.cassandra.amazonaws.com]": 0.015297680999992735, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.ecr.amazonaws.com]": 0.014815854999937983, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[repository.sync.codeconnections.amazonaws.com]": 0.015183115000127145, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[resource-explorer-2.amazonaws.com]": 0.015619757999957073, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rolesanywhere.amazonaws.com]": 0.014979282000126659, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[s3-outposts.amazonaws.com]": 0.01501157300003797, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ses.amazonaws.com]": 0.015308131999859143, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[shield.amazonaws.com]": 0.015337123999984215, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-incidents.amazonaws.com]": 0.015261132000091493, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-quicksetup.amazonaws.com]": 0.015507514999967498, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm.amazonaws.com]": 0.014967258999945443, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[sso.amazonaws.com]": 0.015029808000008416, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[vpcorigin.cloudfront.amazonaws.com]": 0.01532285000007505, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[waf.amazonaws.com]": 0.015048712999941927, - "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[wafv2.amazonaws.com]": 0.014992357999858541, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_service": 0.07315713099990262, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_user": 0.02334109100013393, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_delete_user_after_service_credential_created": 0.0753782570001249, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_id_match_user_mismatch": 0.0911336519999395, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_invalid_update_parameters": 0.07397348700010298, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_list_service_specific_credential_different_service": 0.07273468799996863, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[cassandra.amazonaws.com]": 0.10292836500002522, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[codecommit.amazonaws.com]": 0.10544747400001597, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[satisfiesregexbutstillinvalid]": 0.09055869099984193, - "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[totally-wrong-credential-id-with-hyphens]": 0.08891449099996862, - "tests/aws/services/iam/test_iam.py::TestRoles::test_role_with_tags": 0.07160503199986579, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_add_tags_to_stream": 0.6711604430001898, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_blob_handling": 0.6647312610000427, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_shard_count": 0.6728492789999336, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_stream_name_raises": 0.03763594000008652, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records": 0.7265247620000537, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_empty_stream": 0.6697020009999051, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_next_shard_iterator": 0.6676098279999678, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_shard_iterator_with_surrounding_quotes": 0.6615108760001931, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_record_lifecycle_data_integrity": 0.8696140390002256, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_stream_consumers": 1.3090424370001301, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard": 4.511703542999953, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_cbor_at_timestamp": 1.3250439540000798, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_timeout": 6.322870187000035, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp": 1.6474495499999193, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp_cbor": 0.6497350939999933, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.49463266500004, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisJavaSDK::test_subscribe_to_shard_with_java_sdk_v2_lambda": 9.560378968000009, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_add_tags_to_stream": 0.6659897580000234, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_cbor_blob_handling": 0.6619272810000894, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_shard_count": 0.6513701390000506, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_stream_name_raises": 0.039641826000092806, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records": 0.7084781570000587, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_empty_stream": 0.6568353369999613, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_next_shard_iterator": 0.6675640939999994, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_shard_iterator_with_surrounding_quotes": 0.6636130049998883, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_record_lifecycle_data_integrity": 0.8504922700000179, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_stream_consumers": 1.283638837000126, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard": 4.5421473819998255, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_cbor_at_timestamp": 1.2960533300000634, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_timeout": 6.295773811000004, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp": 4.455819431000123, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp_cbor": 0.6445260330000337, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.512611055999855, - "tests/aws/services/kinesis/test_kinesis.py::TestKinesisPythonClient::test_run_kcl": 21.07721061600023, - "tests/aws/services/kms/test_kms.py::TestKMS::test_all_types_of_key_id_can_be_used_for_encryption": 0.07073580899998433, - "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_delete_deleted_key": 0.03496924400019452, - "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_use_disabled_or_deleted_keys": 0.05491277900011937, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_alias": 0.18977173400014635, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_custom_key_asymmetric": 0.037602972999934536, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_invalid_key": 0.02362099100002979, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_same_name_two_keys": 0.05900936000000456, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_valid_key": 0.04103115699990667, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key": 0.11799160499992922, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_id": 0.027155237999863857, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_hmac": 0.03561395600002015, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_symmetric_decrypt": 0.029584399999976085, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[lowercase_prefix]": 0.08844073700004174, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[too_long_key]": 0.08782073099996524, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[uppercase_prefix]": 0.08818306099999518, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_tag_and_untag": 0.11307849699994676, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_too_many_tags_raises_error": 0.09073152799999207, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_list_delete_alias": 0.05897064699991006, - "tests/aws/services/kms/test_kms.py::TestKMS::test_create_multi_region_key": 0.173996612999872, - "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret": 0.2027835750001259, - "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_and_list_sign_key": 0.03606425200007379, - "tests/aws/services/kms/test_kms.py::TestKMS::test_disable_and_enable_key": 0.0546404970000367, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.058213769000076354, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.038818914999978915, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt_encryption_context": 0.2789244330000429, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_1]": 0.1724824020000142, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_256]": 0.17957854900009806, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_1]": 0.26418563599997924, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_256]": 0.14779535800016674, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_1]": 0.6755498960001205, - "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_256]": 0.3750704420000375, - "tests/aws/services/kms/test_kms.py::TestKMS::test_error_messaging_for_invalid_keys": 0.25019388600003367, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_224-HMAC_SHA_224]": 0.12860104000003503, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_256-HMAC_SHA_256]": 0.1277116499999238, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_384-HMAC_SHA_384]": 0.12630365699999402, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_512-HMAC_SHA_512]": 0.12831764800000656, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1024]": 0.08690645499996208, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[12]": 0.08726539100007358, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1]": 0.08592062500008524, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[44]": 0.08524332999991202, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[91]": 0.08471886800009543, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[0]": 0.08637156200006757, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[1025]": 0.08560553999996046, - "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[None]": 0.09541514399995776, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_does_not_exist": 0.11757955799987485, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_in_different_region": 0.13686458000006496, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_invalid_uuid": 0.10445056400010344, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import": 0.4258316509999531, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_public_key": 0.053223846000037156, - "tests/aws/services/kms/test_kms.py::TestKMS::test_get_put_list_key_policies": 0.050157347999970625, - "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key": 0.12347967199991672, - "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key_invalid_operations": 0.10591083600002094, - "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": 0.31962081900019257, - "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_symmetric": 0.31479082300006667, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_224-HMAC_SHA_256]": 0.10346263899998576, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_256-INVALID]": 0.10234128500007955, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_key_usage": 1.1851932929999975, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_256-some different important message]": 0.18745541200007665, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_512-some important message]": 0.18801610499997423, - "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-INVALID-some important message]": 0.1918289749999076, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[180]": 0.10883126300018375, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[90]": 0.11000916099999358, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_status": 0.05544055999985176, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_encryption_decryption": 0.12848726700008228, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_limits": 0.2249186640001426, - "tests/aws/services/kms/test_kms.py::TestKMS::test_key_with_long_tag_value_raises_error": 0.08914024999990033, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_aliases_of_key": 0.06571536799992828, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_grants_with_invalid_key": 0.01334403199996359, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_keys": 0.02740356500009966, - "tests/aws/services/kms/test_kms.py::TestKMS::test_list_retirable_grants": 0.06857601999990948, - "tests/aws/services/kms/test_kms.py::TestKMS::test_non_multi_region_keys_should_not_have_multi_region_properties": 0.17504642400001558, - "tests/aws/services/kms/test_kms.py::TestKMS::test_plaintext_size_for_encrypt": 0.10736375499993756, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.2675030059999699, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.1348974449999787, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_incorrect_source_key": 0.12295648000008441, - "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_invalid_destination_key": 0.04900854999993953, - "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_key": 1.3078255450000142, - "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_id_and_key_id": 0.056496863999996094, - "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_token": 0.056680007999830195, - "tests/aws/services/kms/test_kms.py::TestKMS::test_revoke_grant": 0.058370913999965524, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_modifies_key_material": 0.11649310900008913, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_is_disabled": 0.5070089880000523, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_that_does_not_exist": 0.08931574999996883, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_with_imported_key_material": 0.10196506699992369, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_non_symmetric_key": 0.43511260000002494, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_disabled": 0.11895593500003088, - "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_enabled": 0.13845774499998242, - "tests/aws/services/kms/test_kms.py::TestKMS::test_schedule_and_cancel_key_deletion": 0.04747387999998409, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P256-ECDSA_SHA_256]": 0.30199930300000233, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P384-ECDSA_SHA_384]": 0.32911423499990633, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.31817362800006777, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_256]": 0.6990502769999694, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_384]": 0.7289949659999593, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_512]": 0.7270505110000158, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 3.1541843659998676, - "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 3.984114024000178, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_1]": 0.1456783630000018, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_256]": 0.09752527699993152, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_1]": 0.3845834390000391, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_256]": 0.27109818099995664, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_1]": 2.1024729090000847, - "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_256]": 0.7028725429998985, - "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_and_untag": 0.12344896399997651, - "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_with_invalid_tag_key": 0.10190174000001662, - "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_key_with_duplicate_tag_keys_raises_error": 0.10284726300005786, - "tests/aws/services/kms/test_kms.py::TestKMS::test_untag_key_partially": 0.11654406700006348, - "tests/aws/services/kms/test_kms.py::TestKMS::test_update_alias": 0.06935020299988537, - "tests/aws/services/kms/test_kms.py::TestKMS::test_update_and_add_tags_on_tagged_key": 0.11594799700003477, - "tests/aws/services/kms/test_kms.py::TestKMS::test_update_key_description": 0.040731647000029625, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P256-ECDSA_SHA_256]": 0.040242459000069175, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P384-ECDSA_SHA_384]": 0.04431632100011029, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.04259506200003216, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_256]": 0.14747876499995982, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_384]": 0.15240860799985967, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_512]": 0.15412545699996372, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 1.1535587590000205, - "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 0.9944839289998981, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key": 0.18438363199982177, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair": 0.13138873100001547, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair_without_plaintext": 0.17320440399998915, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_without_plaintext": 0.18417382599989196, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key": 0.03562212300005285, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair": 0.1164261419999093, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_dry_run": 0.030483510999829377, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext": 0.07063434300005156, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext_dry_run": 0.11360121899997466, - "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_without_plaintext": 0.03006575300003078, - "tests/aws/services/kms/test_kms.py::TestKMSMultiAccounts::test_cross_accounts_access": 1.364478206000058, - "tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.py::test_adding_tags": 17.618595320000054, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_deletion_event_source_mapping_with_dynamodb": 6.181896506999919, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_disabled_dynamodb_event_source_mapping": 12.324714113999903, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_duplicate_event_source_mappings": 5.589167559000202, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_filter_type]": 12.811045469000078, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_multiple_filters]": 0.00421278400006031, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_or_filter]": 12.828023025999983, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[date_time_conversion]": 12.849414683000077, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_false_filter]": 12.803305846999933, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_filter_type]": 12.798462460999986, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[insert_same_entry_twice]": 12.772646709000014, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[numeric_filter]": 12.840055602000007, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[prefix_filter]": 12.82486160999997, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping": 14.840474401999927, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_on_failure_destination_config": 12.12594371299997, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_s3_on_failure_destination": 11.519569965999835, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config": 11.326089269000022, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[[{\"eventName\": [\"INSERT\"=123}]]": 4.583323127999961, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[single-string]": 4.54408812600002, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 14.805367029999957, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 14.779696201999968, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 14.849106240999959, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 14.827315617000068, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[null_item_identifier_failure]": 14.795744531999844, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 14.861492927999961, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failures": 16.016895561999945, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 9.785264443000187, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_dict_success]": 9.841807453000229, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_list_success]": 9.758762290999812, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_batch_item_failure_success]": 9.760390336000228, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_success]": 9.807278994999933, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_esm_with_not_existing_dynamodb_stream": 1.8321601050000709, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisEventFiltering::test_kinesis_event_filtering_json_pattern": 9.198863849999952, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping": 12.125388137000073, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping_multiple_lambdas_single_kinesis_event_stream": 19.430985785000075, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_disable_kinesis_event_source_mapping": 29.253906573000222, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_duplicate_event_source_mappings": 3.4054695489999176, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_esm_with_not_existing_kinesis_stream": 1.4634388730000865, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_empty_provided": 9.23652585900004, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_async_invocation": 20.18446830799985, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_on_failure_destination_config": 9.188722740999765, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_s3_on_failure_destination": 9.26627853500031, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_sns_on_failure_destination_config": 9.23097166000025, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_trim_horizon": 26.32388393200017, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-before-ingestion]": 14.30988184800026, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-while-retrying]": 10.293412844999693, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded_discard_records": 19.36326567800006, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 12.218775344999813, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 12.237706551000201, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 12.196556181000005, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 12.180362638999895, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[null_item_identifier_failure]": 12.2282343090003, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 12.156429376000233, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failures": 12.268365985999935, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 7.1202012509997985, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_dict_success]": 7.1264794250000705, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_list_success]": 7.115159915000277, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_string_success]": 7.103002669000034, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_batch_item_failure_success]": 7.153026898000235, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_success]": 7.129792454999915, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_duplicate_event_source_mappings": 2.5914260949998607, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_event_source_mapping_default_batch_size": 3.449774292000029, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and]": 6.447088074000021, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists]": 6.444704077999859, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-bigger]": 6.442261312000028, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-range]": 6.435069634999991, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-smaller]": 6.475738624999849, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or]": 6.467959553999663, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-filter]": 0.002350788000057946, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-matching]": 0.0028071459998955106, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[prefix]": 6.446877327000038, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single]": 6.465602241999932, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[valid-json-filter]": 7.455787600999884, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping": 6.3760580520001895, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[10000]": 9.56986287299992, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[1000]": 9.565274131000024, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[100]": 9.563229161000208, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[15]": 4.624983090999876, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[10000]": 0.02467828799990457, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[1000]": 8.849265013999911, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[100]": 6.586943801000189, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[20]": 6.417543526999907, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_reserved_concurrency": 8.729988843000228, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_window_size_override": 26.844986694, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_update": 11.667921541999704, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[None]": 1.2423612600002798, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter2]": 1.221960885000044, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter3]": 1.2195191399998748, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[simple string]": 1.2323293590000048, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_esm_with_not_existing_sqs_queue": 1.2138595840001472, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_failing_lambda_retries_after_visibility_timeout": 19.770327300999952, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_fifo_message_group_parallelism": 63.492423156000086, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_message_body_and_attributes_passed_correctly": 4.158576378999896, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_redrive_policy_with_failing_lambda": 17.496102890000202, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures": 0.0027103830000214657, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_empty_json_batch_succeeds": 9.583616634999771, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_invalid_result_json_batch_fails": 13.187769792999916, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_on_lambda_error": 9.386588150000307, - "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_sqs_queue_as_lambda_dead_letter_queue": 6.261018767999985, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_alias_routingconfig": 3.1552886649997163, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_lambda_alias_moving": 3.4440861389998645, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[1]": 1.7117880599998898, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[2]": 1.7361900539999624, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_function_state": 1.2453427440000269, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_different_iam_keys_environment": 3.8220447670000794, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_large_response": 1.6680397919997176, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response": 1.9210207850001098, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response_but_with_custom_limit": 1.6502747029996954, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_large_payloads": 1.8647475800000848, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_ignore_architecture": 1.5481023680001726, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[nodejs]": 7.751879003999875, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[python]": 1.6793106090001402, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_host_prefix_api_operation": 12.745970896000244, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_init_environment": 4.669796517999885, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_no_timeout": 3.656503609000083, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_timed_out_environment_reuse": 0.0028237510000508337, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_with_timeout": 3.645897130999856, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_mixed_architecture": 0.0027278930001557455, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_arm": 0.0025508990001981147, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_x86": 1.862684222000098, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_ulimits": 1.6214824719997978, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_delete_lambda_during_sync_invoke": 0.0019338740000875987, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_recreate_function": 3.41061659900015, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_block": 19.111836570999913, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_crud": 1.2474715890000425, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_update": 1.3923947289999887, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_moves_with_alias": 0.0034627310003543244, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_scheduling": 8.503067579000117, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency": 2.90892140699998, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_on_alias": 3.030468105999944, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency": 11.13560907100009, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency_async_queue": 4.0479171949998545, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_provisioned_overlap": 10.828537647000076, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_error": 1.6392530769999212, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_exit": 0.0027929119999043905, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[body-n\\x87r\\x9e\\xe9\\xb5\\xd7I\\xee\\x9bmt]": 1.3704119960002572, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[message-\\x99\\xeb,j\\x07\\xa1zYh]": 1.3587569979999898, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_error": 7.724072115999888, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit": 0.002044985999873461, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit_segfault": 0.0022485779998078215, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_error": 1.2279882760001328, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_timeout": 42.845438718000196, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_wrapper_not_found": 0.0025748820000899286, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[nodejs16.x]": 0.0035659229999964737, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[python3.10]": 0.0025493539999388304, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[nodejs16.x]": 2.3103818659999433, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[python3.10]": 2.2970784560000084, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event_error": 0.0022915100000773236, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": 2.27740582499996, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": 8.726800637000224, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": 2.3144487830002163, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": 2.6412951820000217, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": 1.6665693320001083, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[python3.10]": 1.649130998000146, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[nodejs16.x]": 15.78540380399977, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[python3.10]": 7.718710119999741, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_qualifier": 1.9250683939999362, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invoke_exceptions": 0.11612386599972524, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_lambda_with_context": 0.002644834999955492, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_upload_lambda_from_s3": 2.211493535999807, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_delete_function": 1.1811220829997637, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_alias": 1.179632579999634, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_concurrency": 1.1566069580001113, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_invocation": 1.5384649199997966, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_tags": 1.1638065110000753, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function": 1.157661823999888, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function_configuration": 1.162100460999909, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_lambda_layer": 0.11214306300007593, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_list_versions_by_function": 1.1438671770001747, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_publish_version": 1.2026576409998597, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": 0.002558571999998094, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": 1.487295515000369, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_default": 2.1338122780000504, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_trim_x_headers": 2.0372386870001264, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[BUFFERED]": 1.9384706020002795, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[None]": 1.9524928150001415, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[RESPONSE_STREAM]": 0.012520760000143127, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_form_payload": 1.9433361319997857, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_headers_and_status": 1.6422535869999138, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invalid_invoke_mode": 1.4746717870002612, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[boolean]": 1.8522178640000675, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[dict]": 1.8684172700002364, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[float]": 1.8433495930000845, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response-json]": 1.846169725000209, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response]": 1.9263922129998718, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[integer]": 1.8708902730002137, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[list-mixed]": 1.8731312140000682, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[string]": 1.853785867000397, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id": 1.5628129730000637, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id_aliased": 1.5684943899998416, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_exception": 1.8436866969998391, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_non_existing_url": 0.01719078599990098, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_persists_after_alias_delete": 3.92642964200013, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_async_invoke_queue_upon_function_update": 98.74572410599967, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_function_update_during_invoke": 0.002090526999836584, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_handler_update": 3.2567757889996756, - "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_versions_with_code_changes": 5.622406739000098, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_async_invoke_with_retry": 11.274494868999682, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_format": 0.030377902000509494, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke": 3.720233369000198, - "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke_url": 3.6500113489996693, - "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_code_signing_not_found_excs": 1.3201948989999437, - "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_function_code_signing_config": 1.2720601729999998, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings": 0.09077846299993553, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size": 1.419623698999942, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size_config_update": 15.341339900000037, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_lifecycle": 1.5461590149999722, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_naming": 1.7765898789999994, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_deletion": 1.2073740369999655, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_update": 1.2051881649999814, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_notfound_and_invalid_routingconfigs": 2.4467347050000114, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_exceptions": 2.7919787389999726, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_lifecycle": 1.3897405950000064, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_filter_criteria_validation": 3.5125639190000015, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_self_managed": 0.001926510000032522, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": 3.4108089540000037, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": 1.913434403999986, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": 0.15165147999999817, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle": 7.886127753000039, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle_delete_function": 6.018441099000029, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_function_name_variations": 16.006942852000066, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_create_lambda_exceptions": 0.2720565579999743, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_delete_on_nonexisting_version": 1.294570407000009, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_arns": 2.952176088999977, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_lifecycle": 3.069651012000577, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-create_function]": 0.13795286699999565, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-delete_function]": 0.12667809099997385, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-get_function]": 0.13763276300002758, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-invoke]": 0.10768307500001129, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-create_function]": 0.19096854599999347, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-delete_function]": 0.11352162099998964, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-get_function]": 0.11545793600001275, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-invoke]": 0.11979958400002033, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-create_function]": 0.13277432500001396, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-delete_function]": 0.812112471000006, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-get_function]": 0.14163628200000744, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-invoke]": 0.15851886799998738, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-create_function]": 0.15979576099999804, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-delete_function]": 0.14009171700001843, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-get_function]": 0.16879998399997476, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-invoke]": 0.011641767000014625, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-create_function]": 0.18024711200001775, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-delete_function]": 0.16522746699999402, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-get_function]": 0.13092419200003746, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-invoke]": 0.14890085399997588, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-create_function]": 0.012570397999979832, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-delete_function]": 0.17053018400000042, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-get_function]": 0.1578295129999958, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-invoke]": 0.01356547000003161, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-create_function]": 0.19726170699999557, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-delete_function]": 0.15658991200001537, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-get_function]": 0.16051515800003813, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-invoke]": 0.15779293599999278, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-create_function]": 0.14724572500000477, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-delete_function]": 0.09224869300001615, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-get_function]": 0.14333493899999894, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-invoke]": 0.11169439100001455, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-create_function]": 0.14071946500001786, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-delete_function]": 0.1095021499999973, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-get_function]": 0.12710369300000934, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-invoke]": 0.10517210500000829, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-create_function]": 0.12031093999999598, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-delete_function]": 0.11544999000000189, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-get_function]": 0.17377984700002003, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-invoke]": 0.1446338269999785, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-create_function]": 0.16043950100004167, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-delete_function]": 0.09433242699998345, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-get_function]": 0.09098074499999598, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-invoke]": 0.14309153099998184, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-create_function]": 0.1684070219999967, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-delete_function]": 0.008698250999998436, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-get_function]": 0.166042420999986, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-invoke]": 0.1656526629999746, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-create_function]": 0.13320327700003531, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-delete_function]": 0.13146450899998285, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-get_function]": 0.15348977700000432, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-invoke]": 0.11025249500002587, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-create_function]": 0.16005686900001592, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-delete_function]": 0.151641394999956, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-get_function]": 0.14370579699999553, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-invoke]": 0.1645519669999942, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-create_function]": 0.1637115519999952, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-delete_function]": 0.12967264400001, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-get_function]": 0.13084278900001323, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-invoke]": 0.0978342919999875, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-create_function]": 0.18520146700001305, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-delete_function]": 0.12564264599998864, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-get_function]": 0.11708897400001206, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-invoke]": 0.1353373050000073, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-create_function]": 0.17279938699999775, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-delete_function]": 0.16415993900000103, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-get_function]": 0.1603950999999597, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-invoke]": 0.09668408900000713, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-create_function]": 0.14413972999997782, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-delete_function]": 0.14804356199999802, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-get_function]": 0.11677773299996375, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-invoke]": 0.11864762200002588, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[delete_function]": 1.3458313280000027, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function]": 1.3207363459999897, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_code_signing_config]": 1.2949810650000018, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_concurrency]": 1.3586450940000248, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_configuration]": 1.2675844830000074, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_event_invoke_config]": 1.251961743999999, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_url_config]": 1.3570228799999882, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[invoke]": 1.310319077999992, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": 0.09394513899999879, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": 0.0016804069999807325, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": 0.42603143499999874, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": 1.6865884579999886, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3_errors": 1.6358565360000057, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_zipfile": 1.7017540770000323, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_code_updates": 2.3198846669999966, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_config_updates": 2.2573654180000062, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_list_functions": 2.8611870360000182, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[delete_function]": 0.14229565999997362, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function]": 0.16486363800001413, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_code_signing_config]": 0.13927940100001024, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_concurrency]": 0.10030983800001536, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_configuration]": 0.17638675899999612, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_event_invoke_config]": 0.13818992400001662, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_url_config]": 0.17408811199999263, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function]": 1.4260611249999897, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_configuration]": 1.373470612999995, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_event_invoke_config]": 1.3625302529999885, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[delete_function]": 0.13961503899997751, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function]": 0.1556593309999812, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function_configuration]": 0.15816385399998012, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_redundant_updates": 19.132282673000006, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_update_lambda_exceptions": 1.3177312720000032, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_vpc_config": 4.0526483100000235, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_and_image_config_crud": 1.501729987999994, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_crud": 3.5042941170000006, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_versions": 1.558020397000007, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_zip_file_to_image": 1.3862848539999675, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes0]": 0.13629585300003555, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes1]": 0.15790750200000048, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_deterministic_version": 0.05918603200001371, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_exceptions": 0.28869791900001474, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_exceptions": 17.471757965999927, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_quota_exception": 16.367925767999964, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_lifecycle": 1.4650442210000278, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_exceptions": 0.9510164609999379, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_lifecycle": 0.1727437960000202, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_s3_content": 0.20588588400005392, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_aws": 1.246217458999979, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_fields": 1.305770869000014, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_create_multiple_lambda_permissions": 1.2314227610000898, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_lambda_permission_fn_versioning": 1.3906080640000482, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_permission_exceptions": 1.3434953770000106, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_remove_multi_permissions": 1.2703956290000065, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_lambda_provisioned_lifecycle": 2.4669343779999906, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_exceptions": 1.385430471999996, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_limits": 1.275007004999992, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_allow": 1.215456156999977, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_default_terminate": 1.2119024030000105, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_invalid_value": 1.2224572189999776, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency": 1.2565041189999988, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_exceptions": 1.2368453109999962, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_limits": 1.2253976750000106, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_basic": 13.694355443999996, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_permissions": 1.2783019789999912, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_version_and_alias": 1.380600751000003, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_lambda_envvars_near_limit_succeeds": 1.9413671529999306, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_fails_multiple_keys": 16.21223549900003, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_variables_fails": 16.215719016000037, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_lambda": 12.655979592999984, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_request_create_lambda": 2.054504942000051, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_unzipped_lambda": 4.773260998999945, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_zipped_create_lambda": 1.5840246529999717, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_exceptions": 0.10462688100005835, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet8]": 4.301632663000021, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java11]": 2.2802056979999747, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java17]": 2.298732555000015, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java21]": 2.2909830649999776, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.12]": 1.2528703000000405, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.13]": 6.336554586999966, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet8]": 1.2363496250000026, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java11]": 1.2322632559999533, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java17]": 1.225515689999952, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java21]": 1.2363539489999766, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.12]": 1.2315538919999653, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.13]": 1.2315788580000344, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_esm_create": 2.1843852740000216, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_fn_create": 1.2411486050000065, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[event_source_mapping]": 0.12942339899998956, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[lambda_function]": 0.12877336999997624, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[event_source_mapping]": 1.4374380980000296, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[lambda_function]": 1.332545873000015, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_nonexisting_resource": 1.2686829609999961, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_exceptions": 1.3001496830000292, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_lifecycle": 1.3755252969999674, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_limits": 1.3483965009999679, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_versions": 1.2524427940000464, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag": 1.137214063999977, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_alias": 3.3923927610000533, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_invalid_id": 1.1383591580000143, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_deletion_without_qualifier": 1.364452417999928, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_exceptions": 7.618796888000077, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_lifecycle": 1.3257573959999718, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_list_paging": 1.3783144559999982, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_version_on_create": 1.264309510000004, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_update": 1.3521178689999829, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_wrong_sha256": 1.2566123160000018, - "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_version_lifecycle": 1.480643437000026, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_advanced_logging_configuration_format_switch": 2.330596097000125, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_advanced_logging_configuration": 1.2901778660002492, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config0]": 1.2754708080001365, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config1]": 2.2810308059997624, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config2]": 1.2899379470004533, - "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config3]": 1.2863811319998604, - "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_cross_region_arn_function_access": 1.1362143549999928, - "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_update_function_configuration_full_arn": 2.231263221000063, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_disabled": 15.201672189999954, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[dotnetcore3.1]": 0.10546813300015856, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[go1.x]": 0.10668007499953092, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[java8]": 0.10719308400030059, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs12.x]": 0.1034576159995595, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs14.x]": 0.10737201199935953, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[provided]": 0.104468968999754, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[python3.7]": 0.10291856299954816, - "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[ruby2.7]": 0.103619358999822, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet6]": 1.9188952129998142, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet8]": 1.8763349590001326, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java11]": 4.865562085000079, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java17]": 4.131626204999975, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java21]": 4.051371957000015, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java8.al2]": 5.791192183000135, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs16.x]": 1.7525139220000483, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs18.x]": 1.7101408379999157, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs20.x]": 1.6808803510000416, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs22.x]": 1.6746037349998915, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.10]": 1.7782494550000365, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.11]": 1.6919605360000105, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.12]": 1.7209622440000203, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.13]": 1.7391179969998802, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.8]": 1.754294171999959, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.9]": 1.722428898999965, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.2]": 2.3574248990000797, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.3]": 2.3578339380001125, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.4]": 2.078441041000019, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet6]": 3.521305716000029, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet8]": 2.5492180470000676, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java11]": 2.470619219000014, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java17]": 2.444410083999969, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java21]": 2.4925703000000112, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java8.al2]": 5.540389053000013, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs16.x]": 8.508149679000041, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs18.x]": 2.4460830660000283, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs20.x]": 2.368929391999984, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs22.x]": 6.4240697210000235, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2023]": 3.1820416980000346, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2]": 4.0700950139998895, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.10]": 6.609435970999982, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.11]": 8.639284853999982, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.12]": 2.535995727999989, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.13]": 2.5594995099999664, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.8]": 2.7437356729999465, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.9]": 6.636842068000021, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.2]": 8.576818453999977, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.3]": 8.621771292999938, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.4]": 8.527660746999913, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet6]": 3.6758299150000084, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet8]": 3.709032281000077, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java11]": 3.714625053000077, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java17]": 3.7123481380000385, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java21]": 3.6968682589999844, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java8.al2]": 3.8907468379999273, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs16.x]": 3.5717326019999973, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs18.x]": 4.34401296700014, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs20.x]": 3.5185748869999998, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs22.x]": 3.5583152670001255, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2023]": 3.5878326300000936, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2]": 3.619004881000137, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.10]": 3.539593924000087, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.11]": 3.549286281000036, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.12]": 3.5476624550000224, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.13]": 3.549181529000066, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.8]": 4.512018911999917, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.9]": 3.4813998580000316, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.2]": 3.64603390700006, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.3]": 3.565767777000133, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.4]": 4.483854023000163, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet6]": 1.8013302559999147, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet8]": 1.8483424280000236, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java11]": 1.9284575599999698, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java17]": 1.8745888599999034, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java21]": 1.831030788000021, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java8.al2]": 2.114985807000153, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs16.x]": 1.7906454060001806, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs18.x]": 1.7338067359999059, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs20.x]": 1.6868069350001633, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs22.x]": 1.695551425999838, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.10]": 1.6675212340001053, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.11]": 1.7184388010000475, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.12]": 1.6865273320001961, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.13]": 1.678681885999822, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.8]": 1.707898182000008, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.9]": 1.6709811660000469, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.2]": 1.7589813040000308, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.3]": 1.773137252999959, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.4]": 1.7688944450000008, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet6]": 1.8450450979999005, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet8]": 1.8148441030000413, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java11]": 1.950761567999848, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java17]": 1.8451102779999928, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java21]": 1.8568430540000236, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java8.al2]": 3.007222504999959, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs16.x]": 1.7344065560000672, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs18.x]": 1.736370784000087, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs20.x]": 1.6972624329999917, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs22.x]": 1.6781753899999785, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2023]": 1.7510858040001267, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2]": 1.7277709730000197, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.10]": 1.6594115689999853, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.11]": 1.6696925319998854, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.12]": 1.707224277000023, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.13]": 1.6958250520001457, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.8]": 1.6938216030000603, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.9]": 1.6927830350000477, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.2]": 1.7714287759998797, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.3]": 1.7545344459999797, - "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.4]": 1.7857379370000217, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDLQ::test_dead_letter_queue": 20.244186640999942, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationEventbridge::test_invoke_lambda_eventbridge": 15.699212226999862, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload0]": 1.9144593099999838, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload1]": 1.8865043770000511, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_lambda_destination_default_retries": 22.291171191000103, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_maxeventage": 63.873615467000036, - "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_retries": 22.4979380850001, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_additional_docker_flags": 1.5614994059998253, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_lambda_docker_networks": 6.389258876999975, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[nodejs20.x]": 3.414213337000092, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[python3.12]": 3.3750850069999387, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_environment_placeholder": 0.4410364320001463, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_error_path_not_absolute": 0.024595613000087724, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_publish_version": 0.12389808100010669, - "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestLambdaDNS::test_lambda_localhost_localstack_cloud_connectivity": 1.5930167200001506, - "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[Active]": 2.6066424230000393, - "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[PassThrough]": 2.6000771199999235, - "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_xray_trace_propagation": 1.5893855760000406, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestCloudwatchLogs::test_multi_line_prints": 3.5839969759999804, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2023]": 1.8600389839999707, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2]": 1.8139093210002102, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2023]": 1.9409844420000582, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2]": 1.9653045799999518, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom-INTERFACE]": 3.0124529060000214, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequest-INTERFACE]": 3.0114100900000267, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequestCustom-CUSTOM]": 2.992728177999993, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_lambda_subscribe_sns_topic": 9.818983201000037, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_runtime_with_lib": 5.602426281000021, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java11]": 2.657126316000017, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java17]": 2.543481368000016, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java21]": 2.752565585999946, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java8.al2]": 2.784869563999905, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java11]": 1.7517226679999567, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java17]": 1.6952873149998595, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java21]": 1.7390845750001063, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java8.al2]": 1.7244664400001284, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs16.x]": 4.689928080999948, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs18.x]": 4.7307155930000135, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs20.x]": 4.676698552000062, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs22.x]": 4.69563985700006, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.10]": 1.6693993800000726, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.11]": 1.6563678210000035, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.12]": 1.6530530219999946, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.13]": 1.6342768800001295, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.8]": 1.664327177999894, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.9]": 1.6652530469999647, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.10]": 1.527464233000046, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.11]": 1.5299295540000912, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.12]": 1.5499214350001012, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.13]": 1.5390513180000198, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.8]": 1.5587840120001601, - "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.9]": 1.52356000199984, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_group": 0.1998627520001719, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_stream": 0.47841317300026276, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_delivery_logs_for_sns": 1.0874242910003886, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_filter_log_events_response_header": 0.053448817999878884, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_list_tags_log_group": 0.22654333300010876, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_metric_filters": 0.0017834079999374808, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_events_multi_bytes_msg": 0.05873772800009647, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_firehose": 0.5406027260003157, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_kinesis": 2.3770668340002885, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_lambda": 1.9140488029997869, - "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_resource_does_not_exist": 0.04024765300027866, - "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend": 0.14214791299991703, - "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend_with_custom_endpoint": 0.16490475700038587, - "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint": 10.485029333999819, - "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint_disabled": 9.90688503299998, - "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_route_through_edge": 9.809311051000122, - "tests/aws/services/opensearch/test_opensearch.py::TestMultiClusterManager::test_multi_cluster": 18.40746871700003, - "tests/aws/services/opensearch/test_opensearch.py::TestMultiplexingClusterManager::test_multiplexing_cluster": 11.414775764999831, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_cloudformation_deployment": 16.253316297000083, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_custom_endpoint": 0.021520834000284594, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_name": 0.02597995499991157, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_existing_domain_causes_exception": 9.858574289999751, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_indices": 11.83938820200001, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_describe_domains": 10.00919320900016, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_lifecycle": 13.588046145999897, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_version": 10.484958120999863, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_path": 10.450651162999748, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_port": 9.885181935000219, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_exception_header_field": 0.012708768000038617, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_version_for_domain": 9.945721866999975, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_versions": 0.02133680299994012, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_document": 10.827773359000275, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_gzip_responses": 10.606430535000072, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_list_versions": 0.09964125200008311, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_search": 11.229968802000258, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_security_plugin": 17.111129382000172, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_sql_plugin": 15.209208234000016, - "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_update_domain_config": 10.485129418999804, - "tests/aws/services/opensearch/test_opensearch.py::TestSingletonClusterManager::test_endpoint_strategy_port_singleton_cluster": 10.248174887999994, - "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_cluster_security_groups": 0.03373815400004787, - "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_create_clusters": 0.17659466800023438, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_cloudformation_query": 0.0017752769999788143, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_create_group": 0.4285522650002349, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_different_region": 0.001795414000071105, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_tag_query": 0.0018047619998924347, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_type_filters": 0.001863251999793647, - "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_search_resources": 0.0017721019999044074, - "tests/aws/services/resourcegroupstaggingapi/test_rgsa.py::TestRGSAIntegrations::test_get_resources": 0.5402695580000909, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_associate_vpc_with_hosted_zone": 0.49774085800004286, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone": 0.6578977589999795, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone_in_non_existent_vpc": 0.19285808099994028, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_private_hosted_zone": 0.7133578640000451, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_crud_health_check": 0.18873839499997302, - "tests/aws/services/route53/test_route53.py::TestRoute53::test_reusable_delegation_sets": 0.16791638499989858, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_associate_and_disassociate_resolver_rule": 0.5067878599998039, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[INBOUND-5]": 0.375540054999874, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[OUTBOUND-10]": 0.3174116149998554, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_query_log_config": 0.31339504200013835, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule": 0.39419418300008147, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule_with_invalid_direction": 0.30221465799991165, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_endpoint": 0.08939368100004685, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_query_log_config": 0.1587490719998641, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_rule": 0.08880460399996082, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_resolver_endpoint": 0.2962837960001252, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_disassociate_non_existent_association": 0.08849458600002436, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_domain_lists": 0.19710694000013973, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules": 0.32864413900006184, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_empty_rule_group": 0.10675211299985676, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_missing_rule_group": 0.16214456100010466, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multipe_create_resolver_rule": 0.42886478800005534, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multiple_create_resolver_endpoint_with_same_req_id": 0.30242885300003763, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_route53resolver_bad_create_endpoint_security_groups": 0.19648598899993885, - "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_update_resolver_endpoint": 0.31520965100003195, - "tests/aws/services/s3/test_s3.py::TestS3::test_access_bucket_different_region": 0.00185526099994604, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_availability": 0.03283692800005156, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_does_not_exist": 0.45003408400020817, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_exists": 0.24390170499987107, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_name_with_dots": 1.7171136910001223, - "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_operation_between_regions": 0.47577165499978946, - "tests/aws/services/s3/test_s3.py::TestS3::test_complete_multipart_parts_order": 0.4713287759998366, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_in_place_with_bucket_encryption": 1.2440475769999466, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_kms": 0.6656655980000323, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character": 0.6626427049995982, - "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character_plus_for_space": 0.09513525900001696, - "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_head_bucket": 0.6529143479997401, - "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_via_host_name": 0.040326119999917864, - "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_existing_name": 0.44220477799990476, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_no_such_bucket": 0.018579694999971252, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy": 0.09888993300000948, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy_expected_bucket_owner": 0.10850173200014979, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_with_content": 0.7512797219997083, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_keys_in_versioned_bucket": 0.539845269000125, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys": 0.08909357400011686, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_in_non_existing_bucket": 0.022151592000000164, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_quiet": 0.11222658600013347, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_object_tagging": 0.14159824499984097, - "tests/aws/services/s3/test_s3.py::TestS3::test_delete_objects_encoding": 0.11118606600007297, - "tests/aws/services/s3/test_s3.py::TestS3::test_different_location_constraint": 0.6095729389996905, - "tests/aws/services/s3/test_s3.py::TestS3::test_download_fileobj_multiple_range_requests": 1.1234054490003018, - "tests/aws/services/s3/test_s3.py::TestS3::test_empty_bucket_fixture": 0.1440795649998563, - "tests/aws/services/s3/test_s3.py::TestS3::test_etag_on_get_object_call": 0.46811941700002535, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_notification_configuration_no_such_bucket": 0.01991648299986082, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy": 0.12245910700016793, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000000000020]": 0.06864487600000757, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000]": 0.0714074069999242, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[aa000000000$]": 0.06991152699993108, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[abcd]": 0.06820072999994409, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_versioning_order": 0.5537823160000244, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_after_deleted_in_versioned_bucket": 0.1173818139998275, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes": 0.313259675999916, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_versioned": 0.5077793189998374, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_with_space": 0.09497176499962734, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[False]": 0.0945076699999845, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[True]": 0.09322100700023839, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_no_such_bucket": 0.021182049999879382, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part": 0.22941350599990074, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[COMPOSITE]": 0.12064480500021091, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[FULL_OBJECT]": 0.12608904299986534, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_with_anon_credentials": 0.4965538139999808, - "tests/aws/services/s3/test_s3.py::TestS3::test_get_range_object_headers": 0.08858752200012532, - "tests/aws/services/s3/test_s3.py::TestS3::test_head_object_fields": 0.09716940899988913, - "tests/aws/services/s3/test_s3.py::TestS3::test_invalid_range_error": 0.09021292500005984, - "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_decoding": 0.4591214700001274, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_and_list_parts": 0.18533429100011745, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_too_small": 0.10720460400034426, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_wrong_part": 0.09813698100015245, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_copy_object_etag": 0.13669795600003454, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_no_such_upload": 0.08861831699982758, - "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_overwrite_key": 0.11692242200001601, - "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[False]": 0.1821338749998631, - "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[True]": 0.18812534299991057, - "tests/aws/services/s3/test_s3.py::TestS3::test_precondition_failed_error": 0.09722920499984866, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_content_language_disposition": 1.9700423639999372, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_hash_prefix": 0.4520646480002597, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_utf8_key": 0.4484632980002061, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_inventory_config_order": 0.15354092099983063, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy": 0.09297900300020956, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_expected_bucket_owner": 0.2556699250001202, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000000000020]": 0.0671064710002156, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000]": 0.0669525580001391, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[aa000000000$]": 0.06968920799999978, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[abcd]": 0.06737610400000449, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_single_character_trailing_slash": 0.15305294599988883, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[a/%F0%9F%98%80/]": 0.4646122229999037, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[file%2Fname]": 0.4993407360002493, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key//]": 0.47162388700007796, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key/]": 0.4674258490001648, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": 0.47057743200002733, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123]": 0.4722018590000516, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%percent]": 0.4641730450000523, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test@key/]": 0.47069319900015216, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_acl_on_delete_marker": 0.533604023999942, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_checksum": 0.10115421200043784, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_content_encoding": 0.09956056700002591, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines": 0.08470452500000647, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig": 0.08104102900006183, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig_empty_body": 0.08239816500008601, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_with_trailing_checksum": 0.10359708400028467, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[DEEP_ARCHIVE-False]": 0.1069318029999522, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER-False]": 0.10180673299987575, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER_IR-True]": 0.10206873499987523, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[INTELLIGENT_TIERING-True]": 0.1054844839998168, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[ONEZONE_IA-True]": 0.10081709999963095, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[REDUCED_REDUNDANCY-True]": 0.10156151299997873, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD-True]": 0.09794581899996047, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD_IA-True]": 0.10291488000007121, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class_outposts": 0.08618506999982856, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_tagging_empty_list": 0.1209801709999283, - "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_with_md5_and_chunk_signature": 0.10636248100036028, - "tests/aws/services/s3/test_s3.py::TestS3::test_putobject_with_multiple_keys": 0.4511025409999547, - "tests/aws/services/s3/test_s3.py::TestS3::test_range_header_body_length": 0.1027530920002846, - "tests/aws/services/s3/test_s3.py::TestS3::test_range_key_not_exists": 0.07022549799989974, - "tests/aws/services/s3/test_s3.py::TestS3::test_region_header_exists_outside_us_east_1": 0.5575720009999259, - "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure": 0.16834449499970106, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_analytics_configurations": 0.21907525399956285, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects": 0.49636887899987414, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects_using_requests_with_acl": 0.0020393590000367112, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_public_objects_using_requests": 0.48610170099982497, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl": 0.15577109199989536, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl_exceptions": 0.19421584199994868, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_content_type_and_metadata": 0.5123313910000888, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_directive_copy": 0.4853453950001949, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_replace": 0.485250029999861, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place": 0.540937365000218, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_metadata_directive": 0.5539221389999511, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_storage_class": 0.4885883320002904, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_suspended_only": 0.5927171109997289, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_versioned": 0.6340088910001214, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_website_redirect_location": 0.4769720049996522, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_with_encryption": 0.7819890319997285, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_preconditions": 3.544989429000225, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_storage_class": 0.49591555900019557, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32C]": 0.49258266199967693, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32]": 0.4873566339997524, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC64NVME]": 0.4914363439997942, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA1]": 0.5067866999997932, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA256]": 0.5209052959999099, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32C]": 0.5222656299999926, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32]": 0.5012983759997951, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC64NVME]": 0.5037229049999041, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA1]": 0.5046414000000823, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA256]": 0.5054016339997816, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_wrong_format": 0.43657010899983106, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[COPY]": 0.5219421849999435, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[None]": 0.4980044740000267, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[REPLACE]": 0.5179609849999451, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[COPY]": 0.5961983299998792, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[None]": 0.5922268260001147, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[REPLACE]": 0.5927994719997969, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_object_with_version_id": 0.5160167080002793, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_objects_trailing_slash": 0.08132115800003703, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_download_object_with_lambda": 4.258083227000043, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_header_overrides": 0.09191276400019888, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_headers": 0.1568702919998941, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[get_object]": 3.5907591859997865, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[head_object]": 3.54825850799989, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_hostname_with_subdomain": 0.020156531999646177, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_intelligent_tier_config": 0.16180414600012227, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_invalid_content_md5": 15.874082557999827, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_inventory_report_crud": 0.1668310930001553, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_lambda_integration": 11.943897006999805, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_acls": 0.21344808299977558, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_sse": 0.20159042099976432, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl": 0.16942613700007314, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl_exceptions": 0.2270615950001229, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expires": 0.5069862329999069, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_inventory_report_exceptions": 0.15637941899990437, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_more_than_1000_items": 13.055225861000054, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_object_versioned": 0.6495486990002064, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_raw_request_routing": 0.10726807599985477, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer": 0.0763872799998353, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer_exceptions": 0.07838640199997826, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_bucket_key_default": 0.231071079000003, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_default_kms_key": 0.00187931599998592, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key": 0.269720469000049, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key_state": 0.2908782499998779, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_timestamp_precision": 0.11145322099991972, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_upload_download_gzip": 0.08875877900004525, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_bucket_name": 0.38310618900004556, - "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_key_names": 0.09606534699969416, - "tests/aws/services/s3/test_s3.py::TestS3::test_set_external_hostname": 0.13703659800012247, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_big_file": 0.6636968980001257, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_multipart": 0.513294144999918, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_with_xml_preamble": 0.4560467560002053, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_cancelled_valid_etag": 0.11093091499969887, - "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_newlines_valid_etag": 0.10080363800011582, - "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[False]": 0.14409543499982647, - "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[True]": 0.14199462599981416, - "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxy_does_not_decode_gzip": 0.10821416700014197, - "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxying_headers": 0.09741384299991296, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_date": 0.09268891400006396, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry": 0.16105357199990067, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry_versioned": 0.1981715270001132, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_multiple_rules": 0.12497705100008716, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_object_size_rules": 0.13032825199979925, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_tag_rules": 0.19423319800011996, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_bucket_lifecycle_configuration": 0.11581621200002701, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_lifecycle_configuration_on_bucket_deletion": 0.1188449670000864, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_lifecycle_expired_object_delete_marker": 0.11508803600008832, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_object_expiry_after_bucket_lifecycle_configuration": 0.12777649499980726, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_put_bucket_lifecycle_conf_exc": 0.1746178950002104, - "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_s3_transition_default_minimum_object_size": 0.1295509920000768, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging": 0.15158777099986764, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_accept_wrong_grants": 0.1338141290002568, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_cross_locations": 0.16500766200033468, - "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_wrong_target": 0.1182297949999338, - "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config": 0.6629851229999986, - "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config_without_filter": 0.6399082620000627, - "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_s3_get_deep_archive_object_restore": 0.546458279999797, - "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_storage_class_deep_archive": 0.16294628000036937, - "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_access": 0.13597080999988975, - "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_copy_object": 0.09273112200003197, - "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_shared_bucket_namespace": 0.07250055300005442, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32C]": 0.4394222420000915, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32]": 0.4532770960001926, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA1]": 0.5160663179999574, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA256]": 0.5363693380002132, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_default": 0.225563763999844, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32C]": 0.5564423470000293, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32]": 0.5307444009999926, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC64NVME]": 0.596412483999984, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object_default": 0.1405255910001415, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32C]": 0.06906223299961312, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32]": 0.07024993300024107, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC64NVME]": 0.06956972200009659, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA1]": 0.07576789999984612, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA256]": 0.07745602799991502, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32C]": 0.07169023600044966, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32]": 0.07076699599997482, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC64NVME]": 0.07188280800028224, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA1]": 0.07404677700014872, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA256]": 0.06920962299977873, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32C]": 0.07088480499987782, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32]": 0.06884618400022191, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC64NVME]": 0.06920191700010037, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA1]": 0.0717537360001188, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA256]": 0.06949538199978633, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_composite": 12.506006361000345, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_full_object": 38.602922144999866, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_size_validation": 0.12867623099987213, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32C]": 8.294175850999864, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32]": 4.286299197999597, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC64NVME]": 10.953125206999857, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA1]": 11.710642906999738, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA256]": 9.283023071999878, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[COMPOSITE]": 0.16237241900012123, - "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[FULL_OBJECT]": 0.1549379030000182, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_delete_locked_object": 0.1271389299997736, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_get_object_legal_hold": 0.13888383099993007, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_legal_hold_exc": 0.16933288500013077, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_with_legal_hold": 0.10717834399997628, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_copy_object_legal_hold": 0.5141496730000199, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_legal_hold_lock_versioned": 0.5501581129999522, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_bucket_config_default_retention": 0.14092775800031632, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_delete_markers": 0.12596479300009378, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_extend_duration": 0.12693311899988657, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_copy_object_retention_lock": 0.4938368550001542, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_lock_mode_validation": 0.10023396700034937, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention": 6.165573406000021, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_compliance_mode": 6.145721475999835, - "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_exc": 0.2531457950001368, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_default_checksum": 0.09193015899995771, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3]": 0.09982769600014763, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3v4]": 0.10204618400007348, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": 0.33764309299999695, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": 0.2886975500000517, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": 0.23784811999985322, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": 0.33678016600015326, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_files": 0.09042961299974195, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_metadata": 0.10247942099999818, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_storage_class": 0.3269749389999106, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[invalid]": 0.16954615999998168, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[list]": 0.16850373299985222, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[notxml]": 0.15802279600029578, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[single]": 0.16654934500002128, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_wrong_content_type": 0.14522381399979167, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_expires": 3.154865770000015, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3]": 0.16715161399974932, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3v4]": 0.1656576689999838, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3]": 0.17096987099989747, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3v4]": 0.17365064099999472, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3]": 0.162991970000121, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3v4]": 0.16169411600003514, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_presigned_post_with_different_user_credentials": 0.19748535199983053, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_redirect": 0.09423778900008983, - "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_status_201_response": 0.08587326199995005, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_delete_has_empty_content_length_header": 0.09465566300013961, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_object_ignores_request_body": 0.08835171700025057, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_request_expires_ignored_if_validation_disabled": 3.1133916910000607, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_head_has_correct_content_length_header": 0.087218939999957, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_forward_slash_bucket": 0.1007802729998275, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_match": 0.09923739400005616, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_none_match": 0.09549571899970033, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_check_signature_validation_for_port_permutation": 0.10360147200003667, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_with_additional_query_params": 0.11535527899968656, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_double_encoded_credentials": 0.1727742230000331, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-False]": 0.22353758100007326, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-True]": 0.22549505599999975, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-False]": 0.23263345899999877, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-True]": 0.23382245500010868, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-False]": 2.179893429999993, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-True]": 2.1782688830001007, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-False]": 2.178100411000287, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-True]": 2.1745945149998533, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-False]": 0.11852045799992084, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-True]": 0.11856198800001039, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-False]": 0.12811406300011186, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-True]": 0.12172147199999017, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_signed_headers_in_qs": 1.9293556239997542, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_x_amz_in_qs": 8.459969730000239, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_different_user_credentials": 0.21244243599971924, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_session_token": 0.1149375659999805, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object": 0.45664109500012273, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-False]": 0.0910562410001603, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-True]": 1.2518580109997401, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-False]": 0.09182878100023117, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-True]": 0.16671733399994082, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[False]": 0.5524784160002127, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[True]": 0.5585767269999451, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[False]": 0.5772252219999245, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[True]": 0.5754834359997858, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_copy_md5": 0.12755802599986055, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_case_sensitive_headers": 0.0935832620000383, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_content_type_same_as_upload_and_range": 0.09664850000012848, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_default_content_type": 0.08430920900013916, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3]": 0.1136306040002637, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3v4]": 0.1050624389999939, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_ignored_special_headers": 0.13024833500026034, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3]": 0.09933383499992487, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3v4]": 0.09893682500000978, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3]": 3.1969215599999643, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3v4]": 3.202107792000106, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3]": 0.17469912600017778, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3v4]": 0.17700933199989777, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_same_header_and_qs_parameter": 0.1866167829996357, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3]": 1.3152372639999612, - "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3v4]": 0.21764730600011717, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32C]": 5.723168185000304, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32]": 3.7948553810001613, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC64NVME]": 6.6037109729998065, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA1]": 1.7797049780001544, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA256]": 4.236675251999941, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_algorithm": 0.11322988799997802, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_automatic_sdk_calculation": 0.2511873169999035, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_with_content_encoding": 0.11221187900014229, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32C]": 0.11868329199978689, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32]": 0.11715090300003794, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC64NVME]": 0.1204019619999599, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[None]": 0.12279665100004422, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA1]": 0.11990208799988977, - "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA256]": 0.11867829500010885, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-False]": 0.09506790699992962, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-True]": 0.09451074000003246, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-False]": 0.09306163000019296, - "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-True]": 0.09448363000001336, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_copy_object_with_sse_c": 0.23190863599984368, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c": 0.4670585070002744, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c_validation": 0.19660169199983102, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_object_retrieval_sse_c": 0.25568737599996894, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_default_checksum_with_sse_c": 0.18338831199980632, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_lifecycle_with_sse_c": 0.1860875070003658, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_validation_sse_c": 0.21419599399996514, - "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_sse_c_with_versioning": 0.232890358999839, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_crud_website_configuration": 0.10791290099973594, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_object_website_redirect_location": 0.2690315729998929, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_conditions": 0.5481198840000161, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_empty_replace_prefix": 0.4251407229999131, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_order": 0.2427056370001992, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_redirects": 0.15834227800019107, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_hosting": 0.5371154250003656, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_index": 0.13997538699982215, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_validate_website_configuration": 0.20557836900002258, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_404": 0.21752592400002868, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_http_methods": 0.14034207899999274, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_index_lookup": 0.2843158149998999, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_no_such_website": 0.14512024600003315, - "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_redirect_all": 0.31232231700005286, - "tests/aws/services/s3/test_s3.py::TestS3TerraformRawRequests::test_terraform_request_sequence": 0.057823583000072176, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_crud": 0.0991369640000812, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_exc": 0.1407482390000041, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_bucket_with_objects": 0.4376769749999312, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_versioned_bucket_with_objects": 0.4822056809998685, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms": 0.22429323400001522, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms_aws_managed_key": 0.2750837879998471, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_s3": 0.10657143799994628, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption": 0.09205349299986665, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption_exc": 0.4813341549997858, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_crud": 0.13802557400026672, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_exc": 0.08686105000015232, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_crud": 0.16090301800022644, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_exc": 0.20793990200013468, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": 0.20966832399994928, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": 0.1348613209997893, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_with_tags": 0.19869228300012765, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_tagging_validation": 0.1812076960000013, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_bucket_ownership_controls_exc": 0.11507628299978023, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_crud_bucket_ownership_controls": 0.1661048460002803, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_crud": 0.119926622000321, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_exc": 0.09671077400025752, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_bucket_versioning_crud": 0.15371966599991538, - "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": 0.09361662899982548, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": 0.08316817199988691, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": 0.08339587599994047, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": 0.08550997000020288, - "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": 0.08501192399990032, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": 0.07923307300006854, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration_twice": 0.08154109799988873, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration": 0.07223560700003873, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration_not_exist": 0.06520752899996296, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations": 0.08071485599998596, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations_paginated": 0.8153219780001564, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_overwrite_bucket_metrics_configuration": 0.15615015200000926, - "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_put_bucket_metrics_configuration": 0.15449889199976496, - "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_no_copy_source_range": 0.18661771899996893, - "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_range": 0.35573241600013716, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object": 0.09017702499977531, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_on_suspended_bucket": 0.5829174090001743, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_versioned": 0.5714011259997278, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects": 0.08672470500005147, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects_versioned": 0.4990260769998258, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_range": 0.36213710000015453, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_with_version_unversioned_bucket": 0.4647931279998829, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_list_object_versions_order_unversioned": 0.5075409590001527, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_put_object_on_suspended_bucket": 0.619508162999864, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_delete_object_with_no_locking": 0.12618885400002, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_disable_versioning_on_locked_bucket": 0.07964561799985859, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_object_lock_configuration_exc": 0.07590854100021716, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_put_object_lock_configuration": 0.10201520900000105, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_exc": 0.12726640699997915, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_on_existing_bucket": 0.1147625070000231, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_etag": 0.14544879100026264, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_delete": 0.140249810999876, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put": 0.15556710800001383, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put_identical": 0.1487221170000339, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_delete": 0.15212769999993725, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_put": 0.10965164600020216, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match": 0.12385156300001654, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_and_if_none_match_validation": 0.06704029699994862, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_validation": 0.08742644199992355, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_versioned_bucket": 0.16700347900018642, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match": 0.10324415199966097, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_validation": 0.0880127299999458, - "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_versioned_bucket": 0.14108138300002793, - "tests/aws/services/s3/test_s3_api.py::TestS3PublicAccessBlock::test_crud_public_access_block": 0.11240134299987403, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_bucket_creation": 0.4153314020002199, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_listing": 0.3278040489999512, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_read": 1.6497841860002609, - "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_read_range": 1.499202034000291, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_expose_headers": 0.2638540220000323, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_get_no_config": 0.11000179200004823, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_no_config": 0.2014467279998371, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket": 0.16652740300014557, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket_ls_allowed": 0.08227199800035123, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_list_buckets": 0.08175168400020993, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_headers": 0.8220074830001067, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_methods": 0.7451311269999223, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_origins": 0.6690663629999563, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_no_config_localstack_allowed": 0.1099034319997827, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_fails_partial_origin": 0.4513579539998318, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_match_partial_origin": 0.1639521490003517, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_delete_cors": 0.19042460099990421, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_get_cors": 0.17040252200013128, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors": 0.1640866000002461, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_default_values": 0.4851501749999443, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_empty_origin": 0.16447625599994353, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_invalid_rules": 0.16477955499999553, - "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_s3_cors_disabled": 0.10095522799997525, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_bucket_region": 0.573113241999863, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_prefix_with_case_sensitivity": 0.4919189519998781, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_when_continuation_token_is_empty": 0.4750652549998904, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_continuation_token": 0.5357998619999762, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_max_buckets": 0.473215429999982, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multipart_uploads_marker_common_prefixes": 0.5127335610002319, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_next_marker": 0.6372573080000166, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_with_prefix_and_delimiter": 0.5102989019999313, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_s3_list_multiparts_timestamp_precision": 0.07889023000029738, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_object_versions_pagination_common_prefixes": 0.591345526999703, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_markers": 0.7062148289999186, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix": 0.6060716559998127, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination": 0.6254950839997946, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination_many_versions": 1.0636382950003735, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_s3_list_object_versions_timestamp_precision": 0.1015020369998183, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_marker_common_prefixes": 0.5458144069998525, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_next_marker": 0.522454535000179, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[%2F]": 0.4668316279996816, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[/]": 1.7250679610001498, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[]": 0.46021442699975523, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_empty_marker": 0.4234840040001018, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjectsV2]": 0.08256811900014327, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjects]": 0.085697601999982, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_common_prefixes": 0.5396686960000352, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_start_after": 0.6622631560001082, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix": 0.516354782999997, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix_and_delimiter": 0.5124669059998723, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_empty_part_number_marker": 0.1450775750001867, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_pagination": 0.139506475999724, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_via_object_attrs_pagination": 0.2905047639997065, - "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_s3_list_parts_timestamp_precision": 0.11013401300010628, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put": 1.8532518159997835, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_in_different_region": 1.8767650999998295, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_versioned": 5.212194300000419, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_put_acl": 1.2903176599998005, - "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_restore_object": 1.1772706000001563, - "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_by_presigned_request_via_dynamodb": 6.119238576000043, - "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_put_via_dynamodb": 2.9577477170000748, - "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_invalid_lambda_arn": 0.48200598800008265, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_not_exist": 0.38291566800012333, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_notifications_with_filter": 1.6407890919999772, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_invalid_topic_arn": 0.2590225750002446, - "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_object_created_put": 1.749566384000218, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_bucket_notification_with_invalid_filter_rules": 0.2695010090001233, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_delete_objects": 0.8152163459999429, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_case_insensitive": 0.09860367099986433, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_invalid_sqs_arn": 0.41656647999980123, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_key_encoding": 1.9166207450000456, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_multiple_invalid_sqs_arns": 0.6144575579999128, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_notifications_with_filter": 0.7405262530000982, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_and_object_removed": 0.8447943570001826, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_complete_multipart_upload": 0.6805891959998007, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_copy": 0.6762774940000327, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put": 0.7064517920000526, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_versioned": 1.0753918200000498, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_with_presigned_url_upload": 0.961646422000058, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_put_acl": 0.8144621750000169, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_delete_event": 0.6810112000000572, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_put_event": 0.6751822100000027, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_restore_object": 0.8048669680001694, - "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_xray_header": 1.6322601659999236, - "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_lifecycle_public_access_block": 0.27462009900023077, - "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_public_access_block_validations": 0.03163235300007727, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_already_exists": 0.0016459400001167523, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_bucket_not_exists": 0.0016548469998269866, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_lifecycle": 0.0016731599998820457, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_name_validation": 0.0016490759999214788, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_pagination": 0.001667840999971304, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_public_access_block_configuration": 0.0016630120001082105, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_crud_public_access_block": 0.0016428239996457705, - "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_empty_public_access_block": 0.001645068000016181, - "tests/aws/services/scheduler/test_scheduler.py::test_list_schedules": 0.0682026979998227, - "tests/aws/services/scheduler/test_scheduler.py::test_tag_resource": 0.03586096100002578, - "tests/aws/services/scheduler/test_scheduler.py::test_untag_resource": 0.031107243999940692, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[ rate(10 minutes)]": 0.01485377700009849, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31)]": 0.014614712999900803, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31T23:59:59Z)]": 0.014744512000106624, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron()]": 0.015471326999886514, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 1 * * * *)]": 0.01734895000004144, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 dummy ? * MON-FRI *)]": 0.015004667999846788, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(7 20 * * NOT *)]": 0.014773266000247531, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(71 8 1 * ? *)]": 0.01482346099987808, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(INVALID)]": 0.016040261000171085, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate( 10 minutes )]": 0.014571398999805751, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate()]": 0.014765521000072113, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(-10 minutes)]": 0.014618937999784976, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 minutess)]": 0.01474846099995375, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 seconds)]": 0.014737832000037088, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 years)]": 0.014193554999792468, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10)]": 0.014729121999835115, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(foo minutes)]": 0.014879544999985228, - "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_valid_schedule_expression": 0.13083872000015617, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times": 0.05309652399978404, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times_snapshots": 0.0017434220001177891, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_can_recreate_delete_secret": 0.0543195260001994, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2]": 0.08739929500029575, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2c3-]": 0.08667266100019333, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name]": 0.08800797399976545, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[s-c64bdc03]": 0.11465293199989901, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets": 0.10452787999997781, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets_snapshot": 0.001805196000077558, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_version_from_empty_secret": 0.04036363699992762, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_with_custom_id": 0.023861888999817893, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_delete_non_existent_secret_returns_as_if_secret_exists": 0.021244566999939707, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version": 0.870167297999842, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version_stage": 0.2015248430000156, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_exp_raised_on_creation_of_secret_scheduled_for_deletion": 0.040899117999742884, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_first_rotate_secret_with_missing_lambda_arn": 0.034685939000155486, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_force_delete_deleted_secret": 0.05671088000030977, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_random_exclude_characters_and_symbols": 0.01582126999983302, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value": 0.07777497699999003, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value_errors": 0.041196602999889365, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_custom_client_request_token_new_version_stages": 0.05698766599994087, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_duplicate_req": 0.049097675999973944, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_null_client_request_token_new_version_stages": 0.06001627499995266, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_duplicate_client_request_token": 0.04873057399981917, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_non_provided_client_request_token": 0.05155352199972185, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv *?!]Name\\\\-]": 0.08881780999990951, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv Name]": 0.08992419000014706, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv*Name? ]": 0.0896563190001416, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[Inv Name]": 0.0952605269999367, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_accessed_date": 0.05367627700002231, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_updated_date": 0.08267975000035221, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_list_secrets_filtering": 0.19506524600001285, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[CreateSecret]": 0.02389609500005463, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[PutSecretValue]": 0.023076371999877665, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[RotateSecret]": 0.022582545999966896, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[UpdateSecret]": 0.02305647400021371, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_no_replacement": 0.2158343649998642, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_replacement": 0.2156453000000056, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_new_custom_client_request_token": 0.050011781999728555, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_version_stages": 0.09854752799992639, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_resource_policy": 0.04984815400007392, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_invalid_lambda_arn": 0.22424415400018916, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_multiple_times_with_lambda_success": 2.8165984899997056, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[None]": 2.335198472000002, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[True]": 2.3140411549998134, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists": 0.048713032000023304, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists_snapshots": 0.050189476000241484, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_not_found": 0.02583593400004247, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_restore": 0.0457702999999583, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_tags": 0.12174562500013053, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_version_not_found": 0.0421941010001774, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_description": 0.10111687599987818, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending": 0.23283985899979598, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle": 0.28581187500026317, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_1": 0.2845326930000738, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_2": 0.3287011069999153, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_3": 0.3187937290001628, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_previous": 0.21220631400001366, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_return_type": 0.048521452999921166, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_with_non_provided_client_request_token": 0.04578820999972777, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access": 0.1390291459999844, - "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access_non_default_key": 0.10752495800011275, - "tests/aws/services/ses/test_ses.py::TestSES::test_cannot_create_event_for_no_topic": 0.04272912400028872, - "tests/aws/services/ses/test_ses.py::TestSES::test_clone_receipt_rule_set": 0.8604735670000991, - "tests/aws/services/ses/test_ses.py::TestSES::test_creating_event_destination_without_configuration_set": 0.06508814400012852, - "tests/aws/services/ses/test_ses.py::TestSES::test_delete_template": 0.05727235800009112, - "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set": 0.015146250000043437, - "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set_event_destination": 0.03043300000013005, - "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_domain": 0.01196982599981311, - "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_email": 0.02596916999982568, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-]": 0.015112587000203348, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-test]": 0.015257774000474456, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-]": 0.014469808000058038, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-test_invalid_value:123]": 0.015235817000075258, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test]": 0.016671825999992507, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test_invalid_value:123]": 0.014858763000120234, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name_len]": 0.015183018000243464, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_value_len]": 0.015464339999880394, - "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_priority_name_value]": 0.015415784000197164, - "tests/aws/services/ses/test_ses.py::TestSES::test_list_templates": 0.15541060900000048, - "tests/aws/services/ses/test_ses.py::TestSES::test_sending_to_deleted_topic": 0.4502077099998587, - "tests/aws/services/ses/test_ses.py::TestSES::test_sent_message_counter": 0.12328041499995379, - "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_email": 1.5206377699998939, - "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_raw_email": 1.503164321000213, - "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_templated_email": 1.514844223999944, - "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-a-this-marketing-campaign]": 0.015658034000125554, - "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-b-that-campaign]": 0.017163996999670417, - "tests/aws/services/ses/test_ses.py::TestSES::test_trying_to_delete_event_destination_from_non_existent_configuration_set": 0.09250700399979905, - "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_email_can_retrospect": 1.5276454940001258, - "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_templated_email_can_retrospect": 0.0813852109997697, - "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[]": 0.18833807100008926, - "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[sns.us-east-1.amazonaws.com]": 0.13609400400014238, - "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_access": 0.12322524499973042, - "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_publish_to_sqs": 0.524601101999906, - "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_access": 0.07942944000001262, - "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_delivery_sqs": 0.14810411299981752, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_create_platform_endpoint_check_idempotency": 0.0018166890001793945, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_disabled_endpoint": 0.1288186750002751, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_gcm": 0.0018942829999559763, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_platform_endpoint_is_dispatched": 0.1451648459997159, - "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_subscribe_platform_endpoint": 0.17568859299990436, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_empty_sns_message": 0.08988685799977247, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_message_structure_json_exc": 0.05960184999980811, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_too_long_message": 0.07522673199991914, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_by_path_parameters": 0.14487609799971324, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_before_subscribe_topic": 0.14773140800002693, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_by_target_arn": 0.22740172999988317, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_non_existent_target": 0.034371017000012216, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_too_long_message": 0.09856106300003376, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_with_empty_subject": 0.04206777800004602, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_wrong_arn_format": 0.037926706000234844, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_topic_publish_another_region": 0.06096262999994906, - "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_unknown_topic_publish": 0.0463309450003635, - "tests/aws/services/sns/test_sns.py::TestSNSPublishDelivery::test_delivery_lambda": 2.0681312380002055, - "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_sms_can_retrospect": 0.2507417300000725, - "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_to_platform_endpoint_can_retrospect": 0.19977998099989236, - "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_subscription_tokens_can_retrospect": 1.0987478130000454, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms": 0.01615507799988336, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms_endpoint": 0.15661520000003293, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_wrong_phone_format": 0.04960570000025655, - "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_subscribe_sms_endpoint": 0.04879245799997989, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_create_subscriptions_with_attributes": 0.08889910800007783, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions": 0.35178556799974103, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions_by_topic_pagination": 1.4964040129998466, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_not_found_error_on_set_subscription_attributes": 0.3081815010000355, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_sns_confirm_subscription_wrong_token": 0.1261721309999757, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_idempotency": 0.1160291490000418, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_protocol": 0.03372104200002468, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_topic": 0.04831580000040958, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_from_non_existing_subscription": 0.0974291159996028, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_idempotency": 0.08875518800005011, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_wrong_arn_format": 0.032207206999828486, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_validate_set_sub_attributes": 0.26972805599984895, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionFirehose::test_publish_to_firehose_with_s3": 1.299714320000021, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[False]": 2.6745675200002097, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[True]": 2.6675567430002047, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_http_subscription_response": 0.0831438509999316, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_multiple_subscriptions_http_endpoint": 2.2421100539995678, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_redrive_policy_http_subscription": 1.1425135689999024, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[False]": 1.6270835289997194, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[True]": 1.633120672999894, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[False]": 1.6053726439999991, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[True]": 1.6170012729999144, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_lambda_url_sig_validation": 2.0299797109998963, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[1]": 4.219454007999957, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[2]": 4.2528216079999765, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_python_lambda_subscribe_sns_topic": 4.235906526999997, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_redrive_policy_lambda_subscription": 1.3134901470000386, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_sns_topic_as_lambda_dead_letter_queue": 2.3732939660001193, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_email_sender": 2.1067091350000737, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_topic_email_subscription_confirmation": 0.06097777199988741, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_attribute_raw_subscribe": 0.14368441199985682, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_empty_or_wrong_message_attributes": 0.3095434970000497, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_not_missing": 0.22509908100005305, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_prefixes": 0.17677870100010296, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_structure_json_to_sqs": 0.20357768899998518, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_exceptions": 0.07275084699995205, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_from_sns_to_sqs": 0.6795921789998829, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_without_topic": 0.03724245100011103, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns": 0.23142330399991806, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns_with_xray_propagation": 0.14042223399997056, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[1]": 0.1501231709999047, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[2]": 0.14602643500006707, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_unicode_chars": 0.13968393099980858, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[False]": 0.20068046699998376, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[True]": 0.21493653900006393, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_sqs_topic_subscription_confirmation": 0.07690201299988075, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_sqs_queue": 0.1751791479998701, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_to_sqs_with_queue_url": 0.05054743000005146, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscription_after_failure_to_deliver": 1.5244170699997994, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[False]": 0.27673774899994896, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[True]": 0.27680877000011606, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[False]": 1.1673449570000685, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[True]": 1.1894118960001379, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs_ordering": 2.5587937039999815, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[False]": 3.617850942000132, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[True]": 3.639586756999961, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[False]": 1.5774708730000384, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[True]": 1.567784243999995, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_deduplication_on_topic_level": 1.676564159000236, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[False]": 0.27001167999992504, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[True]": 1.2447636889999103, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_with_target_arn": 0.03286575099991751, - "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_validations_for_fifo": 0.22941079900010664, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_check_idempotency": 0.09296932799998103, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_with_more_tags": 0.03720639199991638, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_after_delete_with_new_tags": 0.05659422700000505, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_test_arn": 1.5409105019998606, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_with_attributes": 0.2925470249999762, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_delete_topic_idempotency": 0.055603737999945224, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags": 0.0957633570001235, - "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_topic_delivery_policy_crud": 0.0018613409999943542, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy": 0.30988688200022807, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy_attributes_array": 4.286404822999657, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_filter_policy": 5.321526035999568, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_empty_array_payload": 0.168907320000244, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_for_batch": 3.4149609520000013, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_ip_address_condition": 0.3373960230001103, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_large_complex_payload": 0.1931046019999485, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[False]": 5.343124095999883, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[True]": 5.358485362000238, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_attributes": 0.595289768999919, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_of_object_attributes": 0.34156130399992435, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_dot_attribute": 5.586568080000006, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_or_attribute": 0.8793047679998836, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity": 0.05273396499978844, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity_with_or": 0.060242380999852685, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy": 0.12731576900000618, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_exists_operator": 0.11678980599981514, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_nested_anything_but_operator": 0.16285673200013662, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_numeric_operator": 0.22787025000025096, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_string_operators": 0.22425541700022222, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_set_subscription_filter_policy_scope": 0.13856627199970717, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property": 0.12472878899984607, - "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property_constraints": 0.179512466999995, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[domain]": 0.09911282500002017, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[path]": 0.09792690500012213, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[standard]": 0.09994356599986531, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[domain]": 0.03252390900001956, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[path]": 0.032086973000332364, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[standard]": 0.03315877700015335, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs]": 0.09327471699998569, - "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs_query]": 0.09417594499973347, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs]": 3.1298064180000438, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs_query]": 3.1321752249998553, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs]": 0.10826327800032232, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs_query]": 0.11054803100000754, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs]": 0.11687759799974629, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs_query]": 0.1568030240000553, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs]": 2.110060269000087, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs_query]": 2.1180281690001266, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs]": 0.6522346360000029, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs_query]": 0.6680468110000675, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs]": 0.10107759400011673, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs_query]": 0.10191948599981515, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs]": 0.09711954499971398, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs_query]": 0.10093898900004206, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs]": 0.0634853369999746, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs_query]": 0.06407133600009729, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs]": 0.08583203899979708, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs_query]": 0.08856355200009602, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs]": 0.14056397500007733, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs_query]": 0.14145536599994557, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_same_attributes_is_idempotent": 0.036917677000019467, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs]": 0.0889457289999882, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs_query]": 0.08609125299994957, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs]": 0.0017927860001236695, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs_query]": 0.0017550750001191773, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs]": 0.1377809499997511, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs_query]": 0.11649173400019208, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs]": 0.032907684999827325, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs_query]": 0.03423280699985298, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs]": 0.038496824000048946, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs_query]": 0.04013653099991643, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs]": 1.5535620299999664, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs_query]": 1.554043986999659, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs]": 0.04406911699993543, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs_query]": 0.04322765200026879, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs]": 0.0017967850001241459, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs_query]": 0.0018179040000632085, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_attributes_is_idempotent": 0.03959426300002633, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs]": 0.202635219000058, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs_query]": 0.20364056599964897, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_same_attributes_is_idempotent": 0.037110568000343847, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs]": 0.03010939600017082, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs_query]": 0.030158666000033918, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_without_attributes_is_idempotent": 0.03632517400001234, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs]": 0.08090602799984481, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs_query]": 0.0782469079999828, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs]": 0.001807182999982615, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs_query]": 0.0016911949999212084, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_config": 0.03688241300005757, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs]": 0.0016762479999670177, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs_query]": 0.0016379069998038176, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs]": 0.059390533999930994, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs_query]": 0.058526180000171735, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs]": 0.12714038299986896, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs_query]": 0.12459642799990434, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_message_attributes": 0.7762692189996869, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs]": 0.16660478299991155, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs_query]": 0.17320790000030684, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs]": 0.00310794300003181, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs_query]": 0.002977439999995113, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs]": 1.1269617549999111, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs_query]": 1.1300770659997852, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs]": 0.0019226370000069437, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs_query]": 0.0016069790001438378, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-]": 0.1678952049999225, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-invalid:id]": 0.06901766600003612, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.07033371599982274, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-]": 0.06873315600000751, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-invalid:id]": 0.06886861999987559, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.06833863199994994, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs]": 0.6427737629999228, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs_query]": 0.6523900500001218, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs]": 0.128238268999894, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs_query]": 0.13201790699986304, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs]": 0.11101272199994128, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs_query]": 0.11793543899989345, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs]": 0.03595644799997899, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs_query]": 0.03274577200022577, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_disallow_queue_name_with_slashes": 0.0018952170000829938, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs]": 6.175048425999876, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs_query]": 6.998815401000229, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs]": 0.20297363000008772, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs_query]": 0.06041982300007476, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_host_via_header_complete_message_lifecycle": 0.08726496200029032, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_hostname_via_host_header": 0.034265184999867415, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs]": 0.24707101399985731, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs_query]": 0.2486964940001144, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs]": 0.3352012749999176, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs_query]": 0.3340536719999818, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs]": 0.2349986090002858, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs_query]": 0.2349547660001008, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs]": 1.1000067189997935, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs_query]": 1.1027410659999077, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-False]": 1.136146300999826, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-True]": 1.1587474240002393, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-False]": 1.1565789320000022, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-True]": 1.1575785310001265, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-False]": 1.139014103999898, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-True]": 1.1397334129999308, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-False]": 1.1328612499999053, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-True]": 1.1310150989997965, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs]": 1.1828063139998903, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs_query]": 1.182326901000124, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs]": 0.00179085199988549, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs_query]": 0.0016659689999869443, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs]": 0.17746897999973044, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs_query]": 0.17978562299981604, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs]": 0.17577651999999944, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs_query]": 0.16537880299983954, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs]": 0.16101876300012918, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs_query]": 0.16210446600007344, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility": 2.110299280000163, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs]": 2.116972978000149, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs_query]": 2.117998501000102, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs]": 0.2676103809999404, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs_query]": 0.2906758589999754, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs]": 0.25476606500001253, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs_query]": 0.28073787599987554, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs]": 0.13178048999998282, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs_query]": 0.1351074259998768, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs]": 2.1048637729998063, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs_query]": 2.1142352700001084, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_requires_suffix": 0.01505610499998511, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs]": 4.110884762000069, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs_query]": 4.114630950000219, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs]": 0.16072183999972367, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs_query]": 0.16193099799988886, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs]": 0.2429885799999738, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs_query]": 0.24751677300014308, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs]": 0.13150700899973344, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs_query]": 0.1382820419996733, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs]": 2.180092956999715, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs_query]": 2.1723139519999677, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs]": 0.17330533699987427, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs_query]": 0.17462302000012642, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs]": 0.09504998700003853, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs_query]": 0.09580802199980099, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": 0.09041687799981446, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs_query]": 0.09495802199967329, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_list_queues_with_query_auth": 0.023223995999842373, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs]": 0.03197182899998552, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs_query]": 0.039098872000067786, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[domain]": 0.06152136900004734, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[path]": 0.06296096799997031, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[standard]": 0.05247311799985255, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs]": 0.07200214399995275, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs_query]": 0.08549197100001038, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_inflight_message_requeue": 4.59596519899992, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs]": 0.14204472699998405, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs_query]": 0.14392364500031363, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_dead_letter_arn_rejected_before_lookup": 0.0016833799998039467, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs]": 1.2460581999998794, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs_query]": 0.02922904400020343, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs]": 0.03046210599995902, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs_query]": 0.033402954000166574, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs]": 0.04117100499979642, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs_query]": 0.04072091500006536, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues": 0.09936600099990756, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_domain": 0.061573781999868515, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_standard": 0.06210078899994187, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_without_endpoint_strategy": 0.06913096900029814, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_pagination": 0.27795928400018965, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[\"{\\\\\"foo\\\\\": \\\\\"ba\\\\rr\\\\\"}\"]": 0.07869010100034757, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[{\"foo\": \"ba\\rr\", \"foo2\": \"ba"r"\"}]": 0.07670626200001607, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_too_long": 0.16522293100001662, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_group_id_too_long": 0.17062274400018396, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention": 3.0734177649997036, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_fifo": 3.070147973000303, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_with_inflight": 5.61649954800032, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs]": 0.1266984789997423, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs_query]": 0.1277612260000751, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs]": 0.06212093399994956, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs_query]": 0.06273914199982755, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs]": 0.06285444800028017, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs_query]": 0.0636593249996622, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_non_existent_queue": 0.17350115199997163, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs]": 0.23473648800018054, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs_query]": 0.23546649200011416, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs]": 0.0478540299998258, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs_query]": 0.047013567000021794, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs]": 0.09213857200029452, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs_query]": 0.09319603399990228, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs]": 0.16163680400018166, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs_query]": 0.26930144700008896, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs]": 1.2146012529997279, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs_query]": 1.2409186160002719, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs]": 0.09380665499998031, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs_query]": 0.0950284630000624, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs]": 3.152950644999919, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs_query]": 3.142426122000188, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs]": 4.255446161999998, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs_query]": 4.259120046000135, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs]": 0.03170668500001739, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs_query]": 0.029479689000027065, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs]": 1.7304413159999967, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs_query]": 1.9988976390000062, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs]": 1.0929449499999464, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs_query]": 1.0928694390001965, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs]": 0.23411124200015365, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs_query]": 0.2353680399996847, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs]": 0.06591884399995251, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs_query]": 0.07032356600007006, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs]": 0.25871669599973757, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs_query]": 0.264120524999953, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs]": 0.16060556100001122, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs_query]": 0.1657266859997435, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs]": 0.09401009999987764, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs_query]": 0.09562213399999564, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs]": 0.09218310800019935, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs_query]": 0.09375854200015965, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs]": 0.09457840600020972, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs_query]": 0.09571439799992731, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs]": 0.0016855349999786995, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs_query]": 0.0016293799999402836, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs]": 2.0870662579998225, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs_query]": 2.0832449140000335, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_message_size": 0.23460404699994797, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs]": 0.14189553599999272, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs_query]": 0.14125796599978457, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs]": 0.14248172200018416, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs_query]": 0.14370326900007058, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs]": 0.10612342900003569, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs_query]": 0.11085157100023935, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs]": 1.4824780020001072, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs_query]": 2.0008499979999215, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs]": 0.1453394519999165, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs_query]": 0.14505397899984018, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs]": 0.11473856600014187, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs_query]": 0.11849950699979672, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs]": 0.03691457200034165, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs_query]": 0.0328471149998677, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs]": 0.14909878799994658, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs_query]": 0.15028827699984504, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs]": 0.1095938019998357, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs_query]": 0.12286242699997274, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_empty_message_group_id": 0.08777891000022464, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs]": 0.06387528200002635, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs_query]": 0.06466676099989854, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs]": 0.10219840299987482, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs_query]": 0.10351392600000509, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs]": 0.06168832999969709, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs_query]": 0.06354646299996602, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs]": 0.14361298599988004, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs_query]": 0.14414964399998098, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs]": 0.0017906719999700726, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs_query]": 0.001634350000131235, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs]": 0.030380559000150242, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs_query]": 0.030429783999807114, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs]": 0.1358436040000015, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs_query]": 0.1404429720000735, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs]": 0.18641336600012437, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs_query]": 0.1764245580000079, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs]": 0.15228995699976622, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs_query]": 0.17099036300010084, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs]": 0.17457472500018412, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs_query]": 0.17196176599986757, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs]": 0.06464010700005929, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs_query]": 0.06819356200003313, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs]": 0.06274698500010345, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs_query]": 0.06515779800020027, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_multiple_queues": 0.09433695300003819, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs]": 0.2308707830000003, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs_query]": 0.2358662199999344, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs]": 0.07778280500019719, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs_query]": 0.08009695299983832, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs]": 0.0907422539999061, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs_query]": 0.08630696499994883, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs]": 0.06802858100013509, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs_query]": 0.06886873899998136, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs]": 0.06869789099982881, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs_query]": 0.07458955500010234, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs]": 0.04534793100015122, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs_query]": 0.045742569999902116, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": 0.24510995600007845, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs_query]": 0.24509115299974837, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs]": 0.2374887230000695, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs_query]": 0.22602543899984084, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs]": 0.1582025490004071, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs_query]": 0.16316735300006258, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs]": 0.18904850199987777, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs_query]": 0.19974940899987814, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs]": 0.250859175999949, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs_query]": 0.25308331700011877, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs]": 0.0018029970001407492, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs_query]": 0.001606890999710231, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs]": 0.10498465000023316, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs_query]": 0.10893202400006885, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_standard_queue_cannot_have_fifo_suffix": 0.014201958999819908, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs]": 0.1499913350000952, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs_query]": 0.15195382599995355, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs]": 0.07889258799991694, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs_query]": 0.07876665100025093, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs]": 0.042355684999847654, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs_query]": 0.04458670899998651, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs]": 0.104496872000027, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs_query]": 0.10762604200044734, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs]": 0.03552733099991201, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs_query]": 0.03616386499993496, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs]": 0.10259070600000086, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs_query]": 0.10133397800041166, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs]": 0.1412937929999316, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs_query]": 0.14650964199995542, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs]": 0.046061225000130435, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs_query]": 0.04515326500018091, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs]": 1.0615097010002046, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs_query]": 1.060593813999958, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs]": 1.0641250490000402, - "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs_query]": 1.0631451449999076, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[domain]": 0.12506517800011352, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[off]": 0.12199293999969996, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[path]": 0.12734170900012032, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[standard]": 0.18233612300014101, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_create_queue_fails": 0.03540191199999754, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[domain]": 0.05368812699998671, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[path]": 0.05333854200011956, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[standard]": 0.054652306999741995, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails": 0.035111106999920594, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails_json_format": 0.0018026240002200211, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs]": 0.05972939599996607, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs_query]": 0.058928424999976414, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_all": 0.05847465400006513, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_json_format": 0.001628719999871464, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_of_fifo_queue": 0.04191202800006977, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_invalid_arg_returns_error": 0.04408847199988486, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_query_args": 0.04394475300000522, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[domain]": 0.04642186999990372, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[path]": 0.04477248300008796, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[standard]": 0.04592207099994994, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[domain]": 0.05382006100012404, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[path]": 0.05608178300008149, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[standard]": 0.05647828499991192, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[domain]": 0.041637681000111115, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[path]": 0.04023744199980683, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[standard]": 0.0445565060001627, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_send_and_receive_messages": 0.13203492099978575, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_json_format_returns_returns_xml": 0.03357014700009131, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_returns_unknown_operation": 0.034581934000243564, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_invalid_action_raises_exception": 0.03594354200026828, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_overwrite_queue_url_in_params": 0.05529058599995551, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_queue_url_format_path_strategy": 0.023283537999986947, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_send_message_via_queue_url_with_json_protocol": 1.090245118000439, - "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_valid_action_with_missing_parameter_raises_exception": 0.035125156000049174, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-domain]": 0.10708851700019295, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-path]": 0.11070633000031194, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-standard]": 0.10912424499997542, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-domain]": 0.10935156599998663, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-path]": 0.10844019400019533, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-standard]": 0.1112783789999412, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-domain]": 0.08420494100005271, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-path]": 0.08271120599988535, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-standard]": 0.09653832400022111, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-domain]": 0.08814772400000948, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-path]": 0.09025306400008049, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-standard]": 0.09099097500006792, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[domain]": 0.09690993999970487, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[path]": 0.09643208400007097, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[standard]": 0.09848178500010363, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[domain]": 0.10508430499999122, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[path]": 0.10822199700032797, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[standard]": 0.10483104400032062, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[domain]": 0.1081622960000459, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[path]": 0.11030670400009512, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[standard]": 0.10805907100007062, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-domain]": 0.03456210200033638, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-path]": 0.03678374400010398, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-standard]": 0.03955498999971496, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-domain]": 0.03458534900005361, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-path]": 0.03692137500001991, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-standard]": 0.0355329179999444, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[domain]": 0.0199515570004678, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[path]": 0.0220475199998873, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[standard]": 0.02119668999989699, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[domain]": 0.12458284600006664, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[path]": 0.13021906500011937, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[standard]": 0.14369604700004857, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[domain]": 0.025136469000017314, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[path]": 0.025607610999941244, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[standard]": 0.025022817000035502, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[domain]": 0.08414397100023052, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[path]": 0.0839917170001172, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[standard]": 0.0840036390000023, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[domain]": 0.017916554000066753, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[path]": 0.017729676000044492, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[standard]": 0.017986996999979965, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsOverrideHeaders::test_receive_message_override_max_number_of_messages": 0.4812028589997226, - "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsOverrideHeaders::test_receive_message_override_message_wait_time_seconds": 25.268986626000014, - "tests/aws/services/sqs/test_sqs_move_task.py::test_basic_move_task_workflow": 1.8221992099997806, - "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_source_arn_in_task_handle": 0.053841664000174205, - "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_handle": 0.05476087500005633, - "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_id_in_task_handle": 0.07635642999980519, - "tests/aws/services/sqs/test_sqs_move_task.py::test_destination_needs_to_exist": 0.11203146299999389, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_cancel": 1.8318948499997987, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_delete_destination_queue_while_running": 1.8737196699999004, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_with_throughput_limit": 3.387428120999857, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_default_destination": 1.8087098350001725, - "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_multiple_sources_as_default_destination": 2.5800796039998204, - "tests/aws/services/sqs/test_sqs_move_task.py::test_source_needs_redrive_policy": 0.0987667329998203, - "tests/aws/services/sqs/test_sqs_move_task.py::test_start_multiple_move_tasks": 0.7008703510000487, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_describe_parameters": 0.016731235999714045, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_maintenance_window": 0.017289044000108333, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_secret": 0.037005572999987635, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameter_by_arn": 0.06945668699972884, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_and_secrets": 0.13376648399980695, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_by_path_and_filter_by_labels": 0.0779679119998491, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_secret_parameter": 0.08430237700031284, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[///b//c]": 0.0708097799999905, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[/b/c]": 0.08816163300025437, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_parameters_with_path": 0.15994383800034484, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_put_parameters": 0.0809891209999023, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[domain]": 0.1183856189998096, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[path]": 0.11718996599984166, - "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[standard]": 0.1299599730002683, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task": 2.2067284599997947, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_failure": 1.9832994330001839, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_no_worker_name": 1.9402685269997164, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_on_deleted": 0.4305932769998435, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_start_timeout": 5.718760747000033, - "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_with_heartbeat": 7.276879306999945, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY]": 2.273623501000202, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY_GLOBAL_QL_JSONATA]": 2.3592251220002254, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EXPRESSION]": 6.772009595999862, - "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_LITERALS]": 2.4334804280003937, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_FALSE]": 0.7706536440002765, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_TRUE]": 1.0228379579998546, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_CONSTANT_LITERALS]": 1.223822822999864, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_EMPTY]": 0.9708216240001093, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_PATHS]": 1.0370981540002049, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_SCOPE_MAP]": 1.0827912390000165, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_VAR]": 1.3777410200000304, - "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_parallel_cases[BASE_SCOPE_PARALLEL]": 1.1394227490002322, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 2.0424067869998908, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_PARAMETERS]": 1.0843520300004457, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_RESULT]": 1.0589017500001319, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_catch_state": 2.5100548690002142, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[CORRECT]": 1.0797457620001296, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[INCORRECT]": 1.058362692999708, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_wait_state": 0.7754640839998501, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_CHOICE]": 2.3481800699998985, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_FAIL]": 1.0174516189995302, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INPUTPATH]": 0.9529797840000356, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.280231016000016, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 2.0267628309993597, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_OUTPUTPATH]": 0.9753954310003792, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_PARAMETERS]": 0.9827527879999707, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_WAIT]": 1.0460640729997976, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.3087566509998396, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.5952729069999805, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 1.4109662070000013, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 1.5861744379999436, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 6.450969726000039, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 1.9747506079999653, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_PER_BATCH_PATH]": 0.003177933000017674, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_state_assign_evaluation_order[BASE_EVALUATION_ORDER_PASS_STATE]": 0.0021103779999975814, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS]": 0.001975696000044991, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS_FIELD]": 0.0016301929999826825, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ASSIGN]": 1.3260840470002222, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT]": 1.3001550389994918, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_FIELD]": 1.3141110760006995, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_MULTIPLE_STATES]": 1.3657369590000599, - "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_variables_in_lambda_task[BASE_ASSIGN_FROM_LAMBDA_TASK_RESULT]": 2.706206937999923, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_decl_version_1_0": 0.727927698000002, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_base": 3.5984052190000284, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_failure": 0.0026110749999759264, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_execution_dateformat": 0.4019316099999628, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[0]]": 0.7576905740000086, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[10]]": 0.7322190099999943, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[*]]": 0.7262279029999945, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5].itemValue]": 0.7094437920000018, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5]]": 0.7147141119999674, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:]]": 0.7206861839999874, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[:1]]": 0.7148369549999813, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*].itemValue]": 0.7278492770000184, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*]]": 0.6844573479999951, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:].itemValue]": 0.6827354539999817, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:]]": 0.6970786739999824, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1].itemValue]": 0.688811831999999, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1]]": 0.6978602809999757, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$[*]]": 0.686639778, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_query_context_object_values": 1.6132962549999945, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail": 0.7335476390000224, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_empty": 0.7499517239999989, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_intrinsic": 0.8639216660000386, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_path": 0.728897542999988, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path": 0.002678728999995883, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path_base": 0.7267776749999655, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result": 0.7301736170000481, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_jsonpaths": 0.7047313569999858, - "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_null_input_output_paths": 0.778527352999987, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1.5]": 0.7155288369999937, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1]": 0.6934415650000005, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[0]": 1.3303376040000217, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1.5]": 0.7065139689999853, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1]": 1.5230882700000166, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24855]": 0.0024198980000278425, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24856]": 0.0021248079999907077, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000Z]": 0.7796239309999748, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000]": 0.7231681250000292, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.00Z]": 0.6969849930000294, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[Z]": 0.7066860329999827, - "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[]": 0.7219024289999822, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_executions_and_heartbeat_notifications": 0.0017936689999942246, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_heartbeat_notifications": 0.002676079999986314, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sns_publish_wait_for_task_token": 1.5543544119999808, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_PARALLEL_WAIT_FOR_TASK_TOKEN]": 0.009794117000012648, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_WAIT_FOR_TASK_TOKEN_CATCH]": 1.8968731929999763, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_token": 3.0133808269999918, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_tok_with_heartbeat": 7.731577067000018, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token": 2.611082240999991, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_call_chain": 4.260679088000018, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_no_token_parameter": 5.745695385000005, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_timeout": 6.325302176000008, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync": 1.1108989050000275, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync2": 1.1070989739999675, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_failure": 1.9793578490000243, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_timeout": 21.930747863000022, - "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sync_with_task_token": 3.055710932999972, - "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals": 14.63200288600001, - "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals_path": 13.982539031999977, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_boolean": 14.70573101900004, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_null": 13.893451079000045, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_numeric": 14.661342854999987, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_present": 13.918934469000021, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_string": 14.653780764999965, - "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_timestamp": 0.003858386999979757, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals": 21.119740813999954, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals_path": 22.058611766000013, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than": 2.5962847970000666, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals": 2.614948566999999, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals_path": 2.625854631999971, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_path": 2.521360709000021, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than": 2.531610612999998, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals": 3.3887222170000086, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals_path": 2.5123641749999592, - "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_path": 2.5826361059999954, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals": 6.290153478000093, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals_path": 1.4643643420000672, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than": 1.7746039029999565, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals": 1.4416849399999592, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals_path": 1.4081362190000277, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_path": 1.7885712309999349, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than": 1.3943609309999374, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals": 1.5056731319999699, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals_path": 1.4124472239999477, - "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_path": 1.4708808089999366, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals": 7.420784610999988, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals_path": 1.4515098450000323, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than": 1.4497016540000232, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals": 1.4819574219999936, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals_path": 0.6800956069999984, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_path": 0.6790762170000448, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than": 1.4681568179999545, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals": 1.3938267569999994, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals_path": 0.6666472200000157, - "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_path": 0.7006356420000088, - "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comment_in_parameters": 0.4798101390000511, - "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comments_as_per_docs": 7.608217059999959, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_error_cause_path": 1.760954158000004, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$.Execution.Input]": 1.0133495309999603, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$]": 0.7927260770000544, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$.Execution.Input]": 1.0168173119999437, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$]": 0.9891783729999588, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_result_selector": 2.675912541999992, - "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_variable": 1.0527014560000225, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_lambda_task": 2.482445361000032, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke": 3.292524662999938, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke_retry": 5.875888626999938, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_INTRINSIC]": 1.5668410090000293, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_JSONATA]": 1.587789593000025, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH]": 1.5968451969999933, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH_CONTEXT]": 1.5916866580000146, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_VARIABLE]": 1.5859425939999596, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[EMPTY_CREDENTIALS]": 0.9161982239999702, - "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[INVALID_CREDENTIALS_FIELD]": 0.892463279000026, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_invalid_param": 0.0018984760000648748, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_put_item_no_such_table": 2.979374134000011, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_invalid_secret_name": 0.7387744779999821, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_no_such_bucket": 0.7028998609999917, - "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_s3_no_such_key": 0.7574976139999876, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.3396971980000103, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_data_limit_exceeded_on_large_utf8_response": 2.41436766999999, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_start_large_input": 4.840313974000026, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.2019511619999435, - "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_data_limit_exceeded_on_large_utf8_response": 2.2908936900000754, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function": 3.253371131999984, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function_catch": 2.4605418160000454, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_custom_exception": 2.278785757000037, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception": 2.3870966829999816, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception_catch": 2.564425050000011, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_invalid_param": 0.7905804090000288, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_invalid_table_name": 0.8808214860000021, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_no_such_table": 0.8012651950000986, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_invoke_timeout": 6.7986502060000475, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function": 1.8862171890000354, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function_catch": 1.9051859790000663, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_custom_exception": 2.3429492120000077, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception": 2.458743443000003, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch": 2.399800027000083, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.Payload]": 2.447702435999986, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.no.such.path]": 2.3985706550000714, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[None]": 2.3898665790001132, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sfn.py::TestTaskServiceSfn::test_start_execution_no_such_arn": 1.0208262840000089, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_empty_body": 0.0018866750000370303, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue": 1.1584911050000528, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue_no_catch": 1.047299313999929, - "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_sqs_failure_in_wait_for_task_tok": 2.7388182860000825, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE]": 0.8980880749999187, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_FALSE]": 1.0802152269999397, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE]": 0.8815205649998461, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE_FALSE]": 1.3540367550000383, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS]": 1.1421327720000818, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": 1.1361067020000064, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": 1.0898432019999973, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": 1.1103856339999538, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": 1.1082945490001066, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[ITEMS]": 2.26130355600003, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[MAX_CONCURRENCY]": 2.248644808999984, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_COUNT]": 2.2754878530000724, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_PERCENTAGE]": 2.2838903269999946, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[HEARTBEAT_SECONDS]": 3.499149171999875, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[TIMEOUT_SECONDS]": 0.0019945659998938936, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[HEARTBEAT_SECONDS]": 2.523916887999917, - "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[TIMEOUT_SECONDS]": 0.0020180599999548576, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_PASS_RESULT]": 1.3313776769999777, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_RAISE_FAILURE]": 1.2769784979999486, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_catch": 3.0341794109999682, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_query_runtime_memory": 3.108879252000065, - "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_retry": 10.229178052000066, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_PASS_RESULT]": 0.8214766140001757, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_RAISE_FAILURE]": 0.5523078219999888, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_catch": 2.284927823999965, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_query_runtime_memory": 1.4021178289999625, - "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_retry": 9.560948900000085, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_0": 0.697860614000092, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_2": 2.894776188000037, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_contains": 3.1643130639998844, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_get_item": 1.6246594340000229, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_length": 0.7119417259999636, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_partition": 8.159310519999963, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_range": 1.6081908419998854, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_unique": 0.6837955940000029, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_partition": 6.4756410980000965, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_range": 2.3518617920000224, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_decode": 0.9943137600000682, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_encode": 1.0012772079999195, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_context_json_path": 0.6928305599999476, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_escape_sequence": 0.4401949919998742, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_1": 2.5120347210000773, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_2": 2.8344277520000105, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_1": 0.6830107640000733, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_2": 0.6856515769999305, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_hash_calculations.py::TestHashCalculations::test_hash": 1.9087643939999452, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge": 0.6774579220001442, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge_escaped_argument": 0.7203068429998893, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_to_string": 2.7837699279999697, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_string_to_json": 3.4630306999998766, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation_jsonata.py::TestJsonManipulationJSONata::test_parse": 2.1585916810000754, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_add": 6.861535556000035, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random": 1.3876681000000417, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random_seeded": 0.7493549660000554, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations_jsonata.py::TestMathOperationsJSONata::test_math_random_seeded": 0.0022723450000512457, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split": 2.5107704289998765, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split_context_object": 0.6814311850000649, - "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_unique_id_generation.py::TestUniqueIdGeneration::test_uuid": 0.6698558990000265, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_False]": 1.0217462079999677, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_True]": 1.9298369849999517, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_False]": 1.015480471999922, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_True]": 1.002699057999962, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_False]": 1.0150703520000661, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_True]": 1.0180094249999456, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_deleted_log_group": 1.9417195250000532, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_log_group_with_multiple_runs": 1.5744589689999202, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_False]": 0.9870207970000138, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_True]": 0.9474302950000038, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_False]": 0.7407735829998501, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_True]": 0.7455884669999477, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_False]": 0.7309277019999172, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_True]": 0.7541899760000206, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_False]": 1.022281241999849, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_True]": 1.0156246860001374, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_False]": 0.8141594899999518, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_True]": 0.8119246850000081, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_False]": 0.7220605859999978, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_True]": 0.7190713549999828, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_False]": 1.0767330969999875, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_True]": 1.0749884079999674, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_False]": 1.014368902000001, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_True]": 1.019869215999961, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_False]": 0.9528195799998684, - "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_True]": 0.9593043429999852, - "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_happy_path": 0.42499497500000416, - "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_hybrid_path": 0.35933742000008806, - "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_retry_path": 7.241841545000057, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": 1.736450555000033, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": 1.7220141870000134, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": 1.6431588469998815, - "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": 1.7000319229999832, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": 1.0581441109999332, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": 0.9570502079999414, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": 0.9676883019999423, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_retries": 3.3601613700000144, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": 1.018202530999929, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": 0.8596945739999455, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": 1.5223746750000373, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": 1.2651819719999366, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": 1.037404983999977, - "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": 0.9930494470000895, - "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_set": 0.0049684720000868765, - "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_unset": 0.006749888999934228, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": 0.9420344229999955, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": 0.708696854999971, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": 1.0104202319998876, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": 2.0072703570000385, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": 2.655318456000032, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": 0.707261324000001, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": 0.7164503120000063, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": 0.7207313639999029, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": 0.923621670999978, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": 0.7037436399999706, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": 0.9600789229999691, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": 0.7286320140000271, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": 0.7004581689999441, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": 2.373588562000009, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": 0.7102214899999808, - "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": 0.8122123570000213, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSONATA]": 0.4505314709999766, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSON_PATH]": 0.45321492700009003, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_jsonata_query_language_field_downgrade_exception": 0.001793781000060335, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE]": 0.4507703550000315, - "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE_DEFAULT]": 0.4677245389999598, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONATA_TO_JSONPATH]": 2.5338299639998922, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONPATH_TO_JSONATA]": 3.229515350999918, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONATA_TO_JSONPATH]": 2.277080230000138, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONPATH_TO_JSONATA]": 2.256565425000076, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONATA_OUTPUT_TO_JSONPATH]": 0.857374964000087, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONPATH_OUTPUT_TO_JSONATA]": 0.8640240920000224, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_task_dataflow_to_state": 2.3428382709998914, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONATA_ASSIGN_JSONPATH_REF]": 0.8617217580000442, - "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONPATH_ASSIGN_JSONATA_REF]": 0.867029526999886, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_empty": 2.07495988200003, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_states_runtime": 2.4465947620001316, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO]": 0.8109689890000027, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO_JSONATA]": 0.7735860980000098, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_condition_constant_jsonata": 0.5496883380001236, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE]": 0.7512913329999265, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_JSONATA]": 0.715911255999913, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_LITERAL_JSONATA]": 0.7277894159999505, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.737113804000046, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.706103991999953, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.8828545229998781, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.7721790560000272, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_ASSIGN]": 0.7178679160000456, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_OUTPUT]": 0.7103685529999666, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONPATH]": 0.7321078900000657, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_STRING_LITERALS]": 0.7952054630001157, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_cause_jsonata": 0.6706136709999555, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_error_jsonata": 0.6732520719999684, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION]": 0.0018196819999047875, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION_2]": 0.0016148939998856804, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_ERRORPATH]": 0.7115304129999913, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_CONTEXTPATH]": 0.7226751980001609, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_JSONPATH]": 0.7102056790000688, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_CAUSEPATH]": 0.7165781030000744, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_HEARTBEATSECONDSPATH]": 0.001753676000021187, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_INPUTPATH]": 0.7228691599999593, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_OUTPUTPATH]": 0.7218577649999816, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_TIMEOUTSECONDSPATH]": 0.0018261709999478626, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_empty_retry": 2.1670352100001082, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_base": 9.568236944000091, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_extended_input": 9.685087142000043, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_with_retry_extended_input": 10.006379655999922, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_batching_base_json_max_per_batch_jsonata": 0.0020535759999802394, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_decl": 0.8492168600000696, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_first_line": 1.040788192999912, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json": 0.8231672289999779, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items": 0.8428189360001852, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items_jsonata": 0.893831428999988, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[INVALID_ITEMS_PATH]": 1.1746464670000023, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_ITEM_READER]": 1.1283846620000304, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_PREVIOUS]": 0.9844213809999474, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_list_objects_v2": 0.8296496780000098, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_first_row_extra_fields": 0.8401382679999188, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_duplicate_headers": 0.800485652999896, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_extra_fields": 0.8421290850000105, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_first_row_typed_headers": 0.7937710390001484, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[0]": 0.8313290830000142, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[100000000]": 0.8369759859999704, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[2]": 0.8248562630001288, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[-1]": 0.8362398709998615, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[0]": 1.0515551700000287, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[1.5]": 0.024290427999858366, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000000]": 0.8567332079999233, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000001]": 1.9798542940000061, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[2]": 0.8025463989998798, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_json_no_json_list_object": 0.8297449559998995, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state": 0.8486062350000338, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition": 0.90963888400006, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition_legacy": 0.8641028819998837, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch": 0.8070762739999964, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_empty_fail": 1.7340217770000663, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_legacy": 0.782711344000063, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector": 1.7809325780000336, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector_parameters": 1.0976532890000499, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_items_path_from_previous": 0.8592074419999562, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_parameters": 0.8152352680001513, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant": 1.7422793819999924, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant_lambda": 2.9242100479999635, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_item_selector": 0.8364585709999801, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_parameters": 0.8656394299999874, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR]": 1.9224954780000871, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR_JSONATA]": 0.773352200999966, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_parameters": 1.0492084299999078, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_singleton": 1.346522789000005, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[empty]": 0.7306881570000314, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[mixed]": 0.7097333650000337, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[singleton]": 0.7057916239999713, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[boolean]": 0.5654437010000493, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[function]": 0.0018424710000317646, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[null]": 0.7574794669999392, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[number]": 0.7720122409999703, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[object]": 0.7633683950000432, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[string]": 0.7566215120001516, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[boolean]": 0.8033698870000308, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[null]": 0.7707342270000481, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[number]": 0.8157021170001144, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[object]": 0.7928319890000921, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[string]": 1.7060962599999812, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[empty]": 0.715959329000043, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[mixed]": 0.7406657610000593, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[singleton]": 0.7204612930001986, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[boolean]": 0.748603575000061, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[null]": 1.0090442190000886, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[number]": 0.9628757169999744, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[object]": 0.9583963150000727, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[string]": 0.9441792080000369, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[boolean]": 0.8071273170002087, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[null]": 0.7843313380000154, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[number]": 0.7959286020000036, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[object]": 0.7903934530000924, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[string]": 0.7965160820000392, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_label": 0.7267388129999972, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy": 0.8493891640000584, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed": 0.803364836000128, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_item_selector": 0.8412392000000182, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_parameters": 0.873313029999963, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline": 0.8272832430001245, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_item_selector": 0.8425826929999403, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_parameters": 0.8637574140000197, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_reentrant": 1.7146472239999184, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested": 0.9003587699999116, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed": 0.8779113309998365, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed_no_max_max_concurrency": 10.963228196999921, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_no_processor_config": 0.7489806589998125, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_legacy": 1.936457981999979, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_singleton_legacy": 1.3499053689998846, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_result_writer": 1.0602098619998515, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry": 3.7153002479999486, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_legacy": 3.7264748790000795, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_multiple_retriers": 7.744492962999971, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[-1]": 0.7225607910000917, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[0]": 0.7139052100000072, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[1]": 0.7364470980000988, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[NoNumber]": 0.7275135849998833, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[tolerated_failure_count_value0]": 0.7167916769999465, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1.1]": 0.7256526400000212, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1]": 0.7341625200001545, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[0]": 0.6960172459999967, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1.1]": 0.6922397379998984, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100.1]": 0.7286658179999677, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100]": 0.6992413480001005, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1]": 0.8256354279999414, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[NoNumber]": 0.7113120350001054, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[tolerated_failure_percentage_value0]": 0.7210721190000413, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[count_literal]": 0.7282122770000115, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[percentage_literal]": 0.7120884220000789, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[0]": 0.7351190570000199, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[1]": 0.7237598259999913, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[NoNumber]": 0.6945977589999757, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[max_concurrency_value0]": 0.7131694849999803, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path_negative": 0.8057628629999272, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE]": 0.7995876939999107, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE_PARAMETERS]": 0.7385930350001217, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_catch": 0.7460733350000055, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_fail": 0.6927961440001127, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_nested": 1.0529594690000295, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_order": 0.8044165930000418, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_retry": 3.641223369000045, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features": 5.902981637000039, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_jitter_none": 4.459155804000034, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_max_attempts_zero": 2.368149426000059, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_seconds_jsonata": 0.4678589309999097, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[NANOSECONDS]": 0.4723446479999893, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[SECONDS]": 1.4529497429999765, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_DATE]": 0.42309461199999987, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_ISO]": 0.42235495099998843, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_TIME]": 0.41370395200010535, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[JSONATA]": 0.42533501799982787, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_T]": 0.4371268349998445, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_Z]": 0.41718651400003637, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_DATE]": 0.0016007799999897543, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_ISO]": 0.0016890349999130194, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_TIME]": 0.0017225470000994392, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NANOSECONDS]": 0.4720668019999721, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_T]": 0.0018126350000784441, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_Z]": 0.001575080999941747, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[SECONDS]": 0.4716547749999336, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_DATE]": 0.7027106680000088, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_ISO]": 0.694469750000053, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_TIME]": 0.6998457060000192, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NANOSECONDS]": 0.7276443089999702, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_T]": 0.7030923340000754, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_Z]": 0.693231151999953, - "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[SECONDS]": 0.7055503569999928, - "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_path_based_on_data": 6.475704460999964, - "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_step_functions_calling_api_gateway": 11.459726948000025, - "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_wait_for_callback": 17.739100683999936, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_base": 3.098942318000013, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_error": 2.764350325999999, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[HelloWorld]": 3.0603676390001056, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[None]": 3.088056455000128, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[]": 3.0855493089998163, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[request_body3]": 3.1012530579999975, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header1]": 3.1281803749999426, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header2]": 4.296016684000051, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[singleStringHeader]": 0.0029374299999744835, - "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_query_parameters": 3.291116342000123, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_delete_item": 1.0060542610000311, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_get_item": 1.2734236740000142, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_update_get_item": 1.3159821149998834, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_list_secrets": 0.9523272970000107, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[binary]": 1.0840203700001894, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[bytearray]": 1.1655225639999571, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_binary]": 1.1642841880000105, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_str]": 1.1856435270001384, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[str]": 2.1724385459999667, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[bool]": 1.2282547889997204, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[dict]": 1.1678131470000608, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[list]": 1.175317879000204, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[num]": 1.2390212259997497, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[str]": 1.2270245020001767, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template0]": 0.9903019209998547, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template1]": 0.9466814339998564, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution": 1.0101214059998256, - "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution_implicit_json_serialisation": 1.050387720000117, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_DELETE_ITEM]": 1.2874210509999102, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_GET_ITEM]": 1.2833924029998798, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_QUERY]": 1.33088154699999, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_UPDATE_GET_ITEM]": 1.6366975800001455, - "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_invalid_integration": 0.6087015630002952, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task": 0.00197159999993346, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_raise_failure": 0.0017528810001294914, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync": 0.0018343360000017128, - "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync_raise_failure": 0.001729965999857086, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_base": 2.072496074000128, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_malformed_detail": 0.9487016620000759, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_mixed_malformed_detail": 0.9696335299997827, - "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_no_source": 31.186341630000015, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_bytes_payload": 2.074983381000038, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0.0]": 2.094974527999966, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_0]": 3.2369068909999896, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_1]": 2.0342285979997996, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[HelloWorld]": 2.0789787829999113, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[True]": 2.101025088000142, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value5]": 2.0513511309998194, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value6]": 2.052963849999742, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_pipe": 3.6810026829998606, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_string_payload": 2.038484980000021, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_lambda_task_filter_parameters_input": 2.171397405000107, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke": 2.5688667229999282, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_bytes_payload": 2.493081767999911, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0.0]": 2.5662514470000133, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_0]": 2.5350252149996777, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_1]": 2.576865210999813, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[HelloWorld]": 2.6397750720000204, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[True]": 2.5588091839999834, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value5]": 2.5565156799998476, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value6]": 2.596045380000078, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_unsupported_param": 2.539223328999924, - "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_list_functions": 0.0028876510000372946, - "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution": 1.0937358910000512, - "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution_input_json": 1.0658360360000643, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params0-True]": 1.022987503999957, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params1-False]": 1.0260441880000144, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[1]": 2.165960364999819, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[HelloWorld]": 0.9556866030000037, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[None]": 0.9462480580002648, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[True]": 0.9657683110003745, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[]": 0.9450756199998978, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[message1]": 1.063575955999795, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base_error_topic_arn": 0.9700042880001547, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[\"HelloWorld\"]": 1.1017776130001948, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[HelloWorld]": 1.1225670509998054, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[message_value3]": 1.2335124690000612, - "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[{}]": 1.114308623999932, - "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message": 1.119739560999733, - "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_attributes": 1.1882233729998006, - "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_unsupported_parameters": 1.1153602589999991, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING]": 2.3587973579997197, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING_TO_JSONPATH]": 2.508557351999798, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT]": 0.0020758559999194404, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.0017279759999837552, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0017254519998459728, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT]": 0.0016714710000087507, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.0017499469997801498, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0017010849999223865, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT]": 2.3232993019998958, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT_TO_JSONPATH]": 3.4321542319999025, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY]": 3.6072344480000993, - "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY_TO_JSONPATH]": 3.615916601999743, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dump]": 1.5142978230001063, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dumps]": 1.500202599999966, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dump]": 1.5052341719997457, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dumps]": 1.5116651039998033, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_invalid_sm": 0.5822531910000635, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_valid_sm": 1.5962821969999368, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_definition_format_sm": 0.4783933340004296, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_sm_name": 0.4740749320001214, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_exact_duplicate_sm": 0.5215122730003259, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition": 0.5376022350001222, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition_and_role": 0.661560695000162, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_role_arn": 0.6369122450000759, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_update_none": 0.4941847540001163, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_same_parameters": 0.5723944040000788, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_delete_nonexistent_sm": 0.44016328200018506, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution": 0.9824648729997989, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_arn_containing_punctuation": 0.752457621000076, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_invalid_arn": 0.42031728000029034, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_no_such_state_machine": 1.7259466020002492, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_invalid_arn_sm": 0.42618354999990515, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_nonexistent_sm": 0.44644126200000755, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_sm_arn_containing_punctuation": 0.44628849600007925, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_state_machine_for_execution": 0.5254488429998219, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_invalid_arn": 0.45047541799999635, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_no_such_execution": 0.5038135119998515, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_reversed": 0.5411824000002525, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_arn": 0.45301595500018266, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_input": 0.8166205239999726, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_invalid_arn": 0.4328171869999551, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_no_such_state_machine": 0.45014070499996706, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_pagination": 2.0622604170002887, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_versions_pagination": 2.3252965270000914, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms": 0.6024408279999989, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms_pagination": 0.9429754949999278, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution": 0.6179305410000779, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution_idempotent": 1.178527583999994, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_sync_execution": 0.46510112800024217, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_state_machine_status_filter": 0.6682281409998723, - "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_stop_execution": 0.533229160999781, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[\\x00activity]": 0.3496357189999344, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity name]": 0.350229211000169, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\"name]": 0.3485815639999146, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity#name]": 0.3514859060003346, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity$name]": 0.3487139119997664, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity%name]": 0.3545451590000539, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity&name]": 0.34904262099985317, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity*name]": 0.35250618299983216, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity,name]": 0.34487742800001797, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity/name]": 0.34589632300026096, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity:name]": 0.3537236449997181, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity;name]": 0.35990892399991026, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activityname]": 0.34411924999972143, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity?name]": 0.3517576780000127, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity[name]": 0.3508843770000567, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\\\name]": 0.3519576620001317, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x1f]": 0.3455255010003384, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x7f]": 0.3438011240002652, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity]name]": 0.3608971209998799, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity^name]": 0.34746846600000936, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity`name]": 0.38103935200001615, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity{name]": 0.36078845499991985, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity|name]": 0.3502062540001134, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity}name]": 0.34889903300017977, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity~name]": 0.3684723430001213, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[ACTIVITY_NAME_ABC]": 0.4136641099996723, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[Activity1]": 0.4220552469998893, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]": 0.4315563889999794, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name.1]": 0.42051766400004453, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name_123]": 0.41537599999969643, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name.v2]": 0.42478452199998173, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name]": 0.41264902299985806, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activityName.with.dots]": 0.4257316319999518, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity_123.name]": 0.4204205539999748, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_activity_invalid_arn": 0.4417201669998576, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_deleted_activity": 0.36578011599999627, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_deleted": 0.3692730709999523, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_invalid_arn": 0.43619052299982286, - "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_list_activities": 0.39357426700030373, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_create_alias_single_router_config": 0.7130189639999571, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_delete_list": 0.8734192780000285, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_invoke_describe_list": 1.0797981800001253, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_update_describe": 0.7620737180002379, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_no_such_alias_arn": 0.740159593999806, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_revision_with_alias": 0.711598917999936, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_version_with_alias": 0.755619422999871, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_name": 0.7567398890000732, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_router_configs": 0.7769817600001261, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_not_idempotent": 1.9672842069999206, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_with_state_machine_arn": 0.705570335999937, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_idempotent_create_alias": 0.7362318619998405, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_invalid_next_token": 0.7126779010000064, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[0]": 0.798329436000131, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[1]": 0.8284923710000385, - "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_update_no_such_alias_arn": 0.7323000070000489, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_create_describe_delete": 0.7906105419999676, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_activity_task": 0.9537988419997419, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[SYNC]": 0.9135292349999418, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[WAIT_FOR_TASK_TOKEN]": 0.9128231090001009, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_async_describe_history_execution": 1.452219912000146, - "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_sync_execution": 0.7937495109999873, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_deleted_log_group": 0.6032451940000101, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration0]": 0.4751308730001256, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration1]": 0.47821015899990016, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration0]": 0.4272058310000375, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration1]": 0.41183916899990436, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration2]": 0.42204009599981873, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-False]": 0.4962987289998182, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-True]": 0.5022009600002093, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-False]": 0.4987321390001398, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-True]": 0.5126698450001186, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-False]": 0.4919364370000494, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-True]": 0.5006943280000087, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-False]": 0.5004351329998826, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-True]": 0.5131653430000824, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_multiple_destinations": 1.7043944310003099, - "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_update_logging_configuration": 0.6101971200000662, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_list_map_runs_and_describe_map_run": 0.8443068290002884, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_empty_fail": 0.3583748730000025, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[ ]": 0.3516138499999215, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\"]": 0.33437931200001003, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[#]": 0.3320817309997892, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[$]": 0.3332499029997962, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[%]": 0.33421043999987887, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[&]": 0.342441301000008, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[*]": 0.3313493750001726, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[,]": 0.3336547870001141, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[:]": 0.3312265939998724, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[;]": 0.3336637069999142, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[<]": 0.33591233400011333, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[>]": 0.33460937200015906, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[?]": 0.33921088200008853, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[[]": 0.3299688099998548, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\\\]": 0.3336818010002389, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\n]": 0.32980374699991444, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\r]": 0.3340447870000389, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\t]": 0.3352327669999795, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x00]": 0.336151806000089, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x01]": 0.3312355690000004, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x02]": 0.33997100400006275, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x03]": 0.3361043190000146, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x04]": 0.34240654699988227, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x05]": 0.34534753599996293, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x06]": 0.34226107600011346, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x07]": 0.3359413869998207, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x08]": 0.3416088339997714, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0b]": 0.3395259960002477, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0c]": 0.33559220500001175, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0e]": 0.3468195449997893, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0f]": 0.3396224019998044, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x10]": 0.3375553039998067, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x11]": 0.3487865369997962, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x12]": 0.3337389279997751, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x13]": 0.33708997399980944, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x14]": 0.3405328769999869, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x15]": 0.345186510999838, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x16]": 0.3329870730001403, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x17]": 0.3452730460001021, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x18]": 0.33534392599995044, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x19]": 0.3339747999998508, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1a]": 0.33690737400002035, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1b]": 0.33546870999998646, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1c]": 0.37391879799997696, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1d]": 0.40279331399983676, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1e]": 0.343089000999953, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1f]": 0.3355792229999679, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x7f]": 0.3344505029999709, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x80]": 0.33403204999967784, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x81]": 0.3428339289998803, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x82]": 0.34638988000006066, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x83]": 0.3472990490001848, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x84]": 0.35178543900019577, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x85]": 0.3356268270001692, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x86]": 0.3358112710002388, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x87]": 1.5826524859999154, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x88]": 0.3375160250002409, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x89]": 0.33541733000015483, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8a]": 0.35130481300006977, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8b]": 0.33126640199975554, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8c]": 0.3371105979999811, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8d]": 0.3335565699999279, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8e]": 0.3343442909999794, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8f]": 0.3370702120000715, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x90]": 0.3356649989998459, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x91]": 0.3308369799999582, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x92]": 0.3308279020002374, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x93]": 0.3359112809996532, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x94]": 0.3357602670000688, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x95]": 0.3396044400003575, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x96]": 0.3523786000000655, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x97]": 0.3940131490003296, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x98]": 0.3291544609999164, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x99]": 0.3293584959999407, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9a]": 0.33518467799990503, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9b]": 0.3432490840000355, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9c]": 0.3356382719996418, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9d]": 0.33085741000013513, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9e]": 0.3419880399999329, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9f]": 0.33387773400022525, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[]]": 0.32970553700033634, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[^]": 0.3325770530000227, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[`]": 0.3868349229996966, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[{]": 0.3303778690001309, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[|]": 0.33096453400003156, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[}]": 0.3387437900000805, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[~]": 0.33898935899992466, - "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_too_long_fail": 0.35698589000003267, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_create_state_machine": 0.37412981500006026, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[None]": 0.3680524680000872, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list1]": 0.35852040900022075, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list2]": 0.3578666810001323, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list3]": 0.3568265800001882, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list0]": 0.3683644129998811, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list1]": 0.36849571500010825, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list2]": 0.36990632800029744, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list3]": 0.3768548880000253, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list4]": 0.36948111499987135, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine_version": 0.3773529019997568, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys0]": 0.3919847589997971, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys1]": 0.3850491090001924, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys2]": 0.38138341700005185, - "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys3]": 0.38674453300018286, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_DICT]": 0.37213855499999227, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_STRING]": 0.3596986650002236, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[NOT_A_DEF]": 0.3484774489998017, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[ILLEGAL_WFTT]": 0.3718951499997729, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_BASE_NO_STARTAT]": 0.37170899700004156, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_BASE_PASS]": 0.3566204989999733, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_BASE_NO_STARTAT]": 0.35438652499988166, - "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_BASE_PASS]": 0.3957890909998696, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 2.1026667000001, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_PARAMETERS]": 0.9634485769997809, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_RESULT]": 0.9061791990000074, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_EVALUATION_ORDER_PASS_STATE]": 0.9845413819998612, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_CHOICE]": 1.0318707290000475, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_FAIL]": 0.912155512000254, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INPUTPATH]": 0.8984924080000383, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 2.6394591030000356, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 1.8369880929999454, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_OUTPUTPATH]": 0.9620463500000369, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_PARAMETERS]": 0.935708891999866, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_WAIT]": 0.936081386000069, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.2319674149998718, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.2528417109997463, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 1.1780777300002683, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 0.9229710019999402, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 0.9564154270001382, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 1.0331789310000659, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_CONDITION_CONSTANT_JSONATA]": 0.5531384030000481, - "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.5531726500000786, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_express_with_publish": 0.4239521950000835, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_no_version_description": 0.5015585189999001, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_with_version_description": 0.488352427999871, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_publish": 0.4451014500000383, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_version_description_no_publish": 0.4052644360001523, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version": 0.5118908459996874, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version_with_revision": 0.5305690540001251, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_no_publish_on_creation": 0.48047254700009034, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_publish_on_creation": 0.46793189899995014, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_idempotent_publish": 0.5073692800001481, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_delete_version": 0.5242995559999599, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_state_machine_versions_pagination": 0.9750106809999579, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version": 0.6039441829998395, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_invalid_arn": 0.4376582550000876, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_no_such_machine": 0.5116672480000943, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_start_version_execution": 0.8471033099999659, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_update_state_machine": 0.5473624170001585, - "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_version_ids_between_deletions": 0.5004839609998726, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_CHOICE_STATE]": 1.0158849399997507, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_FAIL_STATE]": 0.8258139449999362, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_PASS_STATE]": 0.8381432850001147, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_RESULT_PASS_STATE]": 0.845285069000056, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_SUCCEED_STATE]": 0.8230394559998331, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_PASS_STATE]": 0.9781271720003133, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_RESULT_PASS_STATE]": 0.9466921300002014, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_CHOICE_STATE]": 0.6903647910000927, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_FAIL_STATE]": 0.5128614410000409, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_PASS_STATE]": 0.5207150059998185, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_RESULT_PASS_STATE]": 0.5292794670001513, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_SUCCEED_STATE]": 1.7490808299999117, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_PASS_STATE]": 0.6205535419999251, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_RESULT_PASS_STATE]": 0.6287619459999405, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_CHOICE_STATE]": 1.0250562009998703, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_FAIL_STATE]": 0.8233068660001663, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_PASS_STATE]": 0.8399987230000079, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_RESULT_PASS_STATE]": 0.8447657010003695, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_SUCCEED_STATE]": 0.8323079469998902, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_PASS_STATE]": 0.9401060670002153, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_RESULT_PASS_STATE]": 0.9431610279998495, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[DEBUG]": 2.5075920970000425, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[INFO]": 2.4442264860001615, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[TRACE]": 2.4502230030002465, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[DEBUG]": 2.4256143169998268, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[INFO]": 2.4056804690001172, - "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[TRACE]": 2.4110928749996674, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_choice_state_machine": 5.053939096000022, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_map_state_machine": 1.172710029999962, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_state_machine": 1.5618306590004067, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_state_machines_in_parallel": 1.9600774909999927, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_events_state_machine": 0.0018743040000117617, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_intrinsic_functions": 1.2155312009999761, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_try_catch_state_machine": 10.157903703000102, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_aws_sdk_task": 1.2478497409999818, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_default_logging_configuration": 0.07557619499993962, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-central-1]": 0.001682231000131651, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-west-1]": 0.00167276299976038, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-1]": 0.0023435359998984495, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-2]": 0.0016845259997353423, - "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_run_aws_sdk_secrets_manager": 3.3716829489999327, - "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_no_timeout": 6.029746080000223, - "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_path_timeout": 6.090483666999944, - "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_timeout": 6.0892257589998735, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_lambda": 6.8526323270000375, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda": 6.881025670000099, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda_with_path": 7.069105297000306, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_global_timeout": 5.600357388999782, - "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_service_lambda_map_timeout": 0.0033104139999977633, - "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_assume_role_tag_validation": 0.17630392099977144, - "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_iam_role_chaining_override_transitive_tags": 0.24156959400011146, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_non_existent_role": 0.017626040000322973, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role": 0.2573627649996979, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_saml": 0.0675980379999146, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_web_identity": 0.0509279699997478, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_expiration_date_format": 0.020345005999843124, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[False]": 0.10505947199976617, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[True]": 0.11586689999990085, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_root": 0.015810212999895157, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[False]": 0.0808266070002901, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[True]": 0.23079672799985929, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_federation_token": 0.14277691499978573, - "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_sts_invalid_parameters": 0.06926984000006087, - "tests/aws/services/support/test_support.py::TestConfigService::test_support_case_lifecycle": 0.08082384299973455, - "tests/aws/services/swf/test_swf.py::TestSwf::test_run_workflow": 0.2049842669998725, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_deletion": 0.1725529490001918, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_start_transcription_job": 0.35862903699990056, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_get_transcription_job": 2.510683495999956, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_list_transcription_jobs": 2.637121169999773, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_invalid_length": 32.07814575799989, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_speaker_labels": 0.0018603349999466445, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_happy_path": 3.6356419619996814, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_speaker_diarization": 0.0020208140001614083, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[None-None]": 2.3993131500001255, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-2-None]": 4.631454868999981, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-3-test-output]": 5.073836882000023, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-4-test-output.json]": 2.488900091000005, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-5-test-files/test-output.json]": 5.101305470999932, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-6-test-files/test-output]": 4.958476516000019, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job_same_name": 2.3025282210003297, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.amr-hello my name is]": 2.1618326090001574, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.flac-hello my name is]": 2.1617860859996654, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp3-hello my name is]": 2.1626136489999226, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp4-hello my name is]": 2.181916180000144, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.ogg-hello my name is]": 2.1713822190001792, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.webm-hello my name is]": 2.177281096000115, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mkv-one of the most vital]": 2.173093686999664, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mp4-one of the most vital]": 2.173511481000105, - "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_unsupported_media_format_failure": 3.1868256530001418, - "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_error_injection": 25.739101290000008, - "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_read_error_injection": 25.72102152399998, - "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_write_error_injection": 51.36920898799963, - "tests/aws/test_error_injection.py::TestErrorInjection::test_kinesis_error_injection": 2.1085714740002004, - "tests/aws/test_integration.py::TestIntegration::test_firehose_extended_s3": 0.20488980800041645, - "tests/aws/test_integration.py::TestIntegration::test_firehose_kinesis_to_s3": 26.366424764999692, - "tests/aws/test_integration.py::TestIntegration::test_firehose_s3": 0.3842443920000278, - "tests/aws/test_integration.py::TestIntegration::test_lambda_streams_batch_and_transactions": 29.782978046000153, - "tests/aws/test_integration.py::TestIntegration::test_scheduled_lambda": 16.246051192000095, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.10]": 1.9358268419998694, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.11]": 1.889851002999876, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.12]": 1.9327478349998728, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.13]": 1.9067850949998046, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.8]": 1.8916692489999605, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.9]": 1.9252105380001012, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.10]": 7.888362296999958, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.11]": 7.816710394000211, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.12]": 1.8152685709999332, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.13]": 7.840183338000088, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.8]": 15.844086710000056, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.9]": 1.796425053000121, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.10]": 3.9397928020002837, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.11]": 3.9204446889998508, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.12]": 3.903758045000359, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.13]": 3.9324001500001486, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.8]": 3.9342993340001158, - "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.9]": 3.9531303950000165, - "tests/aws/test_integration.py::test_kinesis_lambda_forward_chain": 0.003270211000199197, - "tests/aws/test_moto.py::test_call_include_response_metadata": 0.007744425000055344, - "tests/aws/test_moto.py::test_call_multi_region_backends": 0.017748496999956842, - "tests/aws/test_moto.py::test_call_non_implemented_operation": 0.04335268400018322, - "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[IO[bytes]]": 0.02381820899995546, - "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[bytes]": 0.02258292400006212, - "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[str]": 0.05657479800015608, - "tests/aws/test_moto.py::test_call_sqs_invalid_call_raises_http_exception": 0.007990353999957733, - "tests/aws/test_moto.py::test_call_with_es_creates_state_correctly": 0.07033969400004025, - "tests/aws/test_moto.py::test_call_with_modified_request": 0.011393060000045807, - "tests/aws/test_moto.py::test_call_with_sns_with_full_uri": 0.005497573999946326, - "tests/aws/test_moto.py::test_call_with_sqs_creates_state_correctly": 3.892826684999818, - "tests/aws/test_moto.py::test_call_with_sqs_invalid_call_raises_exception": 0.007582004000141751, - "tests/aws/test_moto.py::test_call_with_sqs_modifies_state_in_moto_backend": 0.009831599000108326, - "tests/aws/test_moto.py::test_call_with_sqs_returns_service_response": 0.00696827999991001, - "tests/aws/test_moto.py::test_moto_fallback_dispatcher": 0.012594822999972166, - "tests/aws/test_moto.py::test_moto_fallback_dispatcher_error_handling": 0.04073134600025696, - "tests/aws/test_moto.py::test_request_with_response_header_location_fields": 0.10758027800011405, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_localstack_backends": 0.17033344099991155, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_moto_backends": 1.807604332999972, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_dynamodb": 0.3208017730000847, - "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_kinesis": 1.4580476470000576, - "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_api_gateway": 0.49423618700006955, - "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_sns": 0.07801988099981827, - "tests/aws/test_network_configuration.py::TestLambda::test_function_url": 1.1424180579997483, - "tests/aws/test_network_configuration.py::TestLambda::test_http_api_for_function_url": 0.0018948590002310084, - "tests/aws/test_network_configuration.py::TestOpenSearch::test_default_strategy": 10.730376718000116, - "tests/aws/test_network_configuration.py::TestOpenSearch::test_path_strategy": 10.551683253999954, - "tests/aws/test_network_configuration.py::TestOpenSearch::test_port_strategy": 9.92098682100027, - "tests/aws/test_network_configuration.py::TestS3::test_201_response": 0.0915329079998628, - "tests/aws/test_network_configuration.py::TestS3::test_multipart_upload": 0.10354620899988731, - "tests/aws/test_network_configuration.py::TestS3::test_non_us_east_1_location": 0.06547255200007385, - "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[domain]": 0.02737173199989229, - "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[standard]": 0.021642194999913045, - "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_with_external_port": 0.023099448000039047, - "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_without_external_port": 0.025902803000008134, - "tests/aws/test_network_configuration.py::TestSQS::test_path_strategy": 0.021217245999878287, - "tests/aws/test_notifications.py::TestNotifications::test_sns_to_sqs": 0.19046803799983536, - "tests/aws/test_notifications.py::TestNotifications::test_sqs_queue_names": 0.026730210999858173, - "tests/aws/test_serverless.py::TestServerless::test_apigateway_deployed": 0.0353381200000058, - "tests/aws/test_serverless.py::TestServerless::test_dynamodb_stream_handler_deployed": 0.042800817999932406, - "tests/aws/test_serverless.py::TestServerless::test_event_rules_deployed": 101.4819031940001, - "tests/aws/test_serverless.py::TestServerless::test_kinesis_stream_handler_deployed": 0.001866488000132449, - "tests/aws/test_serverless.py::TestServerless::test_lambda_with_configs_deployed": 0.021809455000038724, - "tests/aws/test_serverless.py::TestServerless::test_queue_handler_deployed": 0.0408892890000061, - "tests/aws/test_serverless.py::TestServerless::test_s3_bucket_deployed": 25.06952781899986, - "tests/aws/test_terraform.py::TestTerraform::test_acm": 0.0017875189998903807, - "tests/aws/test_terraform.py::TestTerraform::test_apigateway": 0.0017470939999384427, - "tests/aws/test_terraform.py::TestTerraform::test_apigateway_escaped_policy": 0.0017907819997162733, - "tests/aws/test_terraform.py::TestTerraform::test_bucket_exists": 0.005183562999945934, - "tests/aws/test_terraform.py::TestTerraform::test_dynamodb": 0.0017082120000395662, - "tests/aws/test_terraform.py::TestTerraform::test_event_source_mapping": 0.0017451899998377485, - "tests/aws/test_terraform.py::TestTerraform::test_lambda": 0.0018585919999623002, - "tests/aws/test_terraform.py::TestTerraform::test_route53": 0.0017755960000158666, - "tests/aws/test_terraform.py::TestTerraform::test_security_groups": 0.0016786260000571929, - "tests/aws/test_terraform.py::TestTerraform::test_sqs": 0.0018308599996998964, - "tests/aws/test_validate.py::TestMissingParameter::test_elasticache": 0.001685488000020996, - "tests/aws/test_validate.py::TestMissingParameter::test_opensearch": 0.001716987000008885, - "tests/aws/test_validate.py::TestMissingParameter::test_sns": 0.0017045240001607453, - "tests/aws/test_validate.py::TestMissingParameter::test_sqs_create_queue": 0.0017320649997145665, - "tests/aws/test_validate.py::TestMissingParameter::test_sqs_send_message": 0.0017301220000263129, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_container_starts_non_root": 0.0017555699998865748, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_custom_docker_flags": 0.0016946460000326624, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_logs": 0.0018359200000759301, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_pulling_image_message": 0.0016992940002182877, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_restart": 0.0017874390000542917, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_already_running": 0.0016989630000807665, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_cli_within_container": 0.0017744449999099743, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_wait_stop": 0.0017219780002051266, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_status_services": 0.0016858989999946061, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_volume_dir_mounted_correctly": 0.001669538999976794, - "tests/cli/test_cli.py::TestCliContainerLifecycle::test_wait_timeout_raises_exception": 0.0017619320001358574, - "tests/cli/test_cli.py::TestDNSServer::test_dns_port_not_published_by_default": 0.001722165999808567, - "tests/cli/test_cli.py::TestDNSServer::test_dns_port_published_with_flag": 0.0018036300000403571, - "tests/cli/test_cli.py::TestHooks::test_prepare_host_hook_called_with_correct_dirs": 0.5646799949997785, - "tests/cli/test_cli.py::TestImports::test_import_venv": 0.006620110000312707, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_404_unfortunately_detected_as_s3_request": 0.033164973000111786, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_internal_failure_handler_http_errors": 0.020115994000207138, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_http_errors": 0.0018102310000358557, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_unexpected_errors": 0.00190262499995697, - "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_patch_http_errors": 0.11968750000005457, - "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_http": 0.10102787100004207, - "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https": 0.10284498700002587, - "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https_localhost": 0.061910048999834544, - "tests/integration/aws/test_app.py::TestHttps::test_default_cert_works": 0.06795655600012651, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_return_response": 0.001885552000203461, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_ssl_websockets": 0.0018981959999564424, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websocket_reject_through_edge_router": 0.001879599999938364, - "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websockets_served_through_edge_router": 0.0018520300000091083, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_request_streaming": 0.11282330099993487, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_response_streaming": 0.13082661599992207, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_raw_header_handling": 0.10243968000008863, - "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_response_close_handlers_called_with_router": 0.10230219400000351, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-False]": 0.0018614969999362074, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-True]": 0.001967084999932922, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-False]": 0.001999314000158847, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-True]": 0.0019808700001249235, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-False]": 3.0012672219997967, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-True]": 2.9967929870001626, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-False]": 2.982221544999902, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-True]": 2.7770806500002436, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[CmdDockerClient]": 0.001897513999892908, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[SdkDockerClient]": 21.051356067999905, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[CmdDockerClient]": 0.0018824359999598528, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[SdkDockerClient]": 0.28764718800016453, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[CmdDockerClient]": 0.0018528709999827697, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[SdkDockerClient]": 0.2041497509999317, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[CmdDockerClient]": 0.004455001999986052, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[SdkDockerClient]": 0.26218506700001853, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[CmdDockerClient]": 0.0020065080000222224, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[SdkDockerClient]": 0.24077716699980556, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[CmdDockerClient]": 0.0019091870001375355, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[SdkDockerClient]": 0.24266724400013118, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[CmdDockerClient]": 0.0018607860001793597, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[SdkDockerClient]": 0.23847325400015507, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[CmdDockerClient]": 0.001910429000190561, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[SdkDockerClient]": 0.008570123000026797, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[CmdDockerClient]": 0.004298970000036206, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[SdkDockerClient]": 0.1974646109999867, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[CmdDockerClient]": 0.0019047569999202096, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[SdkDockerClient]": 0.35381568699995114, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[CmdDockerClient]": 0.0018375530000867002, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[SdkDockerClient]": 0.19309905300019636, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[CmdDockerClient]": 0.0018933469998501096, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[SdkDockerClient]": 0.007692230999964522, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[CmdDockerClient]": 0.0018688610000481276, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[SdkDockerClient]": 0.11690677399997185, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[CmdDockerClient]": 0.0018568880000202626, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[SdkDockerClient]": 1.1934842919999937, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[CmdDockerClient]": 0.0018764339999961521, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[SdkDockerClient]": 0.028313041999808775, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[CmdDockerClient]": 0.0018707950000589335, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[SdkDockerClient]": 0.21646246099976452, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[CmdDockerClient]": 0.001871154999889768, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[SdkDockerClient]": 0.22802477999994153, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-False]": 0.0018873750000238942, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-True]": 0.0018616990000737133, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-False]": 0.2005589330001385, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-True]": 0.21389012499980709, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-False]": 0.001853844000152094, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-True]": 0.0019403549999879033, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-False]": 0.1861031329999605, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-True]": 0.20897852000007333, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[CmdDockerClient]": 0.0018447949998972035, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[SdkDockerClient]": 0.00489601000026596, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[CmdDockerClient]": 0.0018707949998315598, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[SdkDockerClient]": 0.029593726000030074, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[CmdDockerClient]": 0.001838323999891145, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[SdkDockerClient]": 0.02652189500008717, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[CmdDockerClient]": 0.0018249899999318586, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[SdkDockerClient]": 0.00178340999991633, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[CmdDockerClient]": 0.001865925000174684, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[SdkDockerClient]": 0.8200655069999812, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[CmdDockerClient]": 0.0074231730000065, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[SdkDockerClient]": 0.006096622000086427, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[CmdDockerClient]": 0.001857259999951566, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[SdkDockerClient]": 0.2409194300003037, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[CmdDockerClient]": 0.0019743469999866647, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[SdkDockerClient]": 0.23378570499994566, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[CmdDockerClient]": 0.0018665379998310527, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[SdkDockerClient]": 0.03454836599985356, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[CmdDockerClient]": 0.0018436339998970652, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[SdkDockerClient]": 0.23222202800002378, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[CmdDockerClient]": 0.0018760350001230108, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[SdkDockerClient]": 0.28296928700001445, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[CmdDockerClient]": 0.0018406679998861364, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[SdkDockerClient]": 0.23212463499999103, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[CmdDockerClient]": 0.0018491529999664635, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[SdkDockerClient]": 0.23416792000034548, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[CmdDockerClient]": 0.0018841189998966001, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[SdkDockerClient]": 0.22789900599991597, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[CmdDockerClient]": 0.001811113000030673, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[SdkDockerClient]": 0.005977350999955888, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[CmdDockerClient]": 0.0019335829999818088, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[SdkDockerClient]": 0.11516448399993351, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[CmdDockerClient]": 0.0019388819998766849, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[SdkDockerClient]": 0.6158694759999435, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[CmdDockerClient]": 0.001848644000119748, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[SdkDockerClient]": 0.007475356000213651, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[CmdDockerClient]": 0.0018765249999432854, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[SdkDockerClient]": 0.11720685300042533, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[CmdDockerClient]": 0.0018188069998359424, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[SdkDockerClient]": 0.45650517200010654, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[CmdDockerClient]": 0.0019827440000881325, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[SdkDockerClient]": 0.22770437199983462, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[CmdDockerClient]": 0.0018855319999602216, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[SdkDockerClient]": 0.0073477299997648515, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[CmdDockerClient]": 0.0018785189997743146, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[SdkDockerClient]": 0.21054682300018612, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[CmdDockerClient]": 0.0018821650000973023, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[SdkDockerClient]": 0.042097077999869725, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[CmdDockerClient]": 0.0018871339998440817, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[SdkDockerClient]": 0.4258853410001393, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[CmdDockerClient]": 0.0018651350001164246, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[SdkDockerClient]": 0.18741261399986797, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[CmdDockerClient]": 0.0018541819999882136, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[SdkDockerClient]": 0.35380297799997606, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[CmdDockerClient]": 0.001847881999992751, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[SdkDockerClient]": 0.005942125999808923, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[CmdDockerClient]": 0.0019463549999727547, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[SdkDockerClient]": 0.2143282309996266, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[CmdDockerClient]": 0.0019523379999100143, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[SdkDockerClient]": 0.011948622999852887, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[CmdDockerClient]": 0.0018646940000053291, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[SdkDockerClient]": 0.17678084400017724, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[CmdDockerClient]": 0.0018962130000090838, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[SdkDockerClient]": 0.0071018690000528295, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[CmdDockerClient]": 0.0018405180001082044, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[SdkDockerClient]": 0.029265400999747726, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[CmdDockerClient]": 0.0018273929999850225, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[SdkDockerClient]": 0.4147513959999287, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[CmdDockerClient]": 0.001845827999886751, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[SdkDockerClient]": 0.011390744999971503, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[CmdDockerClient]": 0.0018376040000021021, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[SdkDockerClient]": 0.023195329999680325, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[CmdDockerClient]": 0.0038393320000977837, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[SdkDockerClient]": 0.02518147000000681, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[CmdDockerClient]": 0.001847961000066789, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[SdkDockerClient]": 0.22306292299981578, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[CmdDockerClient]": 0.0018575409999357362, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[SdkDockerClient]": 0.0018005020001510275, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[CmdDockerClient]": 0.002001828999937061, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[SdkDockerClient]": 0.19236036699999204, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[CmdDockerClient]": 0.001966562999996313, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[SdkDockerClient]": 0.039522654999700535, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[CmdDockerClient]": 0.002022878999923705, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[SdkDockerClient]": 0.1457680979997349, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[CmdDockerClient]": 0.0019617850000486214, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[SdkDockerClient]": 0.008095113999843306, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[CmdDockerClient]": 0.001860064999846145, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[SdkDockerClient]": 20.39828339099995, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[CmdDockerClient]": 0.0018810740002663806, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[SdkDockerClient]": 0.11129991999996491, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[CmdDockerClient]": 0.0019051690003379917, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[SdkDockerClient]": 0.09211571199989521, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[CmdDockerClient]": 0.0018463600001723535, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[SdkDockerClient]": 0.007259673000135081, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[CmdDockerClient]": 0.001878649000218502, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[SdkDockerClient]": 0.008493326000234447, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[CmdDockerClient]": 0.0018322120001812436, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[SdkDockerClient]": 0.22628330999987156, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[CmdDockerClient]": 0.00184946499985017, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[SdkDockerClient]": 0.005632435999814334, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[CmdDockerClient]": 0.0018424830000185466, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[SdkDockerClient]": 0.44587946699994063, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[CmdDockerClient]": 0.0019613639999533916, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[SdkDockerClient]": 0.44863708599996244, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[CmdDockerClient]": 0.0018592029998671933, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[SdkDockerClient]": 0.6219626329996117, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[CmdDockerClient]": 0.0019117200001801393, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[SdkDockerClient]": 0.6026058329998705, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[CmdDockerClient]": 0.0018307000000277185, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[SdkDockerClient]": 0.11648849300013353, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[CmdDockerClient]": 0.001868600000079823, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[SdkDockerClient]": 0.7780169019999903, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[CmdDockerClient]": 0.001891053000008469, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[SdkDockerClient]": 0.015281970999922123, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[CmdDockerClient]": 0.001884479999944233, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[SdkDockerClient]": 0.007192989000031957, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[CmdDockerClient]": 0.0019681159999436204, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[SdkDockerClient]": 0.005632877999914854, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[CmdDockerClient]": 0.0018339260000175273, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[SdkDockerClient]": 0.0058879620000880095, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[CmdDockerClient]": 0.0018794109998907516, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[SdkDockerClient]": 0.18085054599987416, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[CmdDockerClient]": 0.0018963720001465845, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[SdkDockerClient]": 0.8364539939998394, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[CmdDockerClient]": 0.0018361300001288328, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[SdkDockerClient]": 0.11583301599966944, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[CmdDockerClient]": 0.0018365809999068006, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[SdkDockerClient]": 0.13539972099988518, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[CmdDockerClient]": 0.004478887999766812, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[SdkDockerClient]": 0.19535667099967213, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[CmdDockerClient]": 0.0018736500001068634, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[SdkDockerClient]": 0.19053857899984905, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[CmdDockerClient]": 0.001820019999968281, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[SdkDockerClient]": 0.1800748969999404, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[CmdDockerClient]": 0.0018988369999988208, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[SdkDockerClient]": 10.817034253000202, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-echo]": 0.0018663770001694502, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-entrypoint1]": 0.0018811929999174026, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-echo]": 0.19519624600002317, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-entrypoint1]": 0.19986136700003954, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[CmdDockerClient]": 0.0019803779998710525, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[SdkDockerClient]": 0.005748690999780592, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[CmdDockerClient]": 0.0018507069999031955, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[SdkDockerClient]": 0.00673561499979769, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[CmdDockerClient]": 0.0018591230000311043, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[SdkDockerClient]": 0.19364066500020272, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[CmdDockerClient]": 0.001908135000121547, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[SdkDockerClient]": 0.006095485000287226, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[CmdDockerClient]": 0.0018868140000449785, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[SdkDockerClient]": 0.1492013600000064, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[CmdDockerClient]": 0.003858328000205802, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[SdkDockerClient]": 0.007085118000077273, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[CmdDockerClient]": 0.001948208999920098, - "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[SdkDockerClient]": 0.005733561000170084, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[CmdDockerClient]": 0.003393796999944243, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[SdkDockerClient]": 0.8924711800000296, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[CmdDockerClient]": 0.001865404000000126, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[SdkDockerClient]": 0.006653923999920153, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[CmdDockerClient]": 0.0018564069998774357, - "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[SdkDockerClient]": 0.00614470400000755, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[CmdDockerClient]": 0.003361494999808201, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[SdkDockerClient]": 0.04200879000018176, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[CmdDockerClient]": 0.0018951489996652526, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[SdkDockerClient]": 1.2029843789996448, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[CmdDockerClient]": 0.0020025120002173935, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[SdkDockerClient]": 0.1940223440005866, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[CmdDockerClient]": 0.0019627739998213656, - "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[SdkDockerClient]": 0.18497167399982573, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[CmdDockerClient]": 0.0019893940000201837, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[SdkDockerClient]": 3.2049605709999014, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[CmdDockerClient]": 0.003416386999788301, - "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[SdkDockerClient]": 0.20091753999986395, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[CmdDockerClient]": 0.0062547980000999814, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[SdkDockerClient]": 0.4132094889998825, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[CmdDockerClient]": 0.0018266310000853991, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[SdkDockerClient]": 0.9008440480001809, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[CmdDockerClient]": 0.00198888399995667, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[SdkDockerClient]": 0.19678830399993785, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[CmdDockerClient]": 0.001968966000049477, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[SdkDockerClient]": 0.1925479600001836, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[CmdDockerClient]": 0.0020059549999587034, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[SdkDockerClient]": 0.14086766400009765, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[CmdDockerClient]": 0.0019035239999993792, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[SdkDockerClient]": 0.19852408200017635, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[CmdDockerClient]": 0.00201808799988612, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[SdkDockerClient]": 0.1671448170000076, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_no_retries": 0.03163822700003038, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_after_init": 1.0862872859997879, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_on_init": 1.0448662140001943, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_timeout_seconds": 0.020202623999921343, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[CmdDockerClient]": 0.0019813999999769294, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[SdkDockerClient]": 0.3603389889999562, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[CmdDockerClient]": 0.0033770860000004177, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[SdkDockerClient]": 0.17388124799981597, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[CmdDockerClient]": 0.0018460480000612733, - "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[SdkDockerClient]": 0.20517400800008545, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[CmdDockerClient]": 0.003689858000370805, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[SdkDockerClient]": 0.3962995869999304, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[CmdDockerClient]": 0.0019055440000101953, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[SdkDockerClient]": 0.3708884079997006, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[CmdDockerClient]": 0.0019008080000730843, - "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[SdkDockerClient]": 0.027523408999968524, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-None]": 0.001878344000033394, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-tcp]": 0.00186683200013249, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-udp]": 0.0019642160000330477, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-None]": 1.466095550000091, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-tcp]": 1.500632024000197, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-udp]": 1.4724878620002073, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-None]": 0.0034285740002815146, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-tcp]": 0.0018720430000485067, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-udp]": 0.001843860000008135, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-None]": 2.605194524000126, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-tcp]": 2.6458283240001492, - "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-udp]": 2.8677484329998606, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[CmdDockerClient]": 0.00343335799993838, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[SdkDockerClient]": 0.37846535699986816, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-False]": 0.0020137900000918307, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-True]": 0.0018948580000142101, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-False]": 0.1263490080000338, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-True]": 0.1219050239999433, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[CmdDockerClient]": 0.0018617860002905218, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[SdkDockerClient]": 0.19842333999963557, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[CmdDockerClient]": 0.0018402360001346096, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[SdkDockerClient]": 0.7248823369998263, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[CmdDockerClient]": 0.0018844280000394065, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[SdkDockerClient]": 0.36401056599993353, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[CmdDockerClient]": 0.006778326000130619, - "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[SdkDockerClient]": 0.1817087019999235, - "tests/integration/services/test_internal.py::TestHealthResource::test_get": 0.01844970999945872, - "tests/integration/services/test_internal.py::TestHealthResource::test_head": 0.014088488000197685, - "tests/integration/services/test_internal.py::TestInfoEndpoint::test_get": 0.043010890999994444, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[boot-True]": 0.01933668800029409, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[ready-True]": 0.019029602999580675, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[shutdown-False]": 0.018874925000091025, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[start-True]": 0.025554576000104134, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_nonexisting_stage": 0.018177142000695312, - "tests/integration/services/test_internal.py::TestInitScriptsResource::test_stages_have_completed": 1.5139917730002708, - "tests/integration/test_config_endpoint.py::test_config_endpoint": 0.040408454000044, - "tests/integration/test_config_service.py::TestConfigService::test_put_configuration_recorder": 0.18049410999992688, - "tests/integration/test_config_service.py::TestConfigService::test_put_delivery_channel": 0.16420913199954157, - "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher": 0.006905179999648681, - "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher_avoid_fallback": 0.004845495999688865, - "tests/integration/test_security.py::TestCSRF::test_CSRF": 0.11141514900009497, - "tests/integration/test_security.py::TestCSRF::test_additional_allowed_origins": 0.017176993999783008, - "tests/integration/test_security.py::TestCSRF::test_cors_apigw_not_applied": 0.04420767600004183, - "tests/integration/test_security.py::TestCSRF::test_cors_s3_override": 0.08649855600060619, - "tests/integration/test_security.py::TestCSRF::test_default_cors_headers": 0.012753316000271298, - "tests/integration/test_security.py::TestCSRF::test_disable_cors_checks": 0.017189247000260366, - "tests/integration/test_security.py::TestCSRF::test_disable_cors_headers": 0.020130637000420393, - "tests/integration/test_security.py::TestCSRF::test_internal_route_cors_headers[/_localstack/health]": 0.017756050999651052, - "tests/integration/test_security.py::TestCSRF::test_no_cors_without_origin_header": 0.010770402000616741, - "tests/integration/test_stores.py::test_nonstandard_regions": 0.16715960900000937, - "tests/integration/utils/test_diagnose.py::test_diagnose_resource": 0.48011295099968265 + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_lambda_dynamodb": 1.9956983250000349, + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_opensearch_crud": 3.410615108000002, + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_search_books": 62.90618743699997, + "tests/aws/scenario/bookstore/test_bookstore.py::TestBookstoreApplication::test_setup": 94.196222235, + "tests/aws/scenario/kinesis_firehose/test_kinesis_firehose.py::TestKinesisFirehoseScenario::test_kinesis_firehose_s3": 0.003267081999979382, + "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_destination_sns": 5.590200782000068, + "tests/aws/scenario/lambda_destination/test_lambda_destination_scenario.py::TestLambdaDestinationScenario::test_infra": 13.268183396999916, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_prefill_dynamodb_table": 24.737072572000045, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input0-SUCCEEDED]": 3.9390597010000192, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input1-SUCCEEDED]": 2.8722224079999705, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input2-FAILED]": 0.894955308999954, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input3-FAILED]": 0.6881113030000279, + "tests/aws/scenario/loan_broker/test_loan_broker.py::TestLoanBrokerScenario::test_stepfunctions_input_recipient_list[step_function_input4-FAILED]": 0.5062714590000041, + "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_deployed_infra_state": 0.002605218999974568, + "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_populate_data": 0.001752393000003849, + "tests/aws/scenario/mythical_mysfits/test_mythical_misfits.py::TestMythicalMisfitsScenario::test_user_clicks_are_stored": 0.0016772440000067945, + "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_notes_rest_api": 4.61587242600001, + "tests/aws/scenario/note_taking/test_note_taking.py::TestNoteTakingScenario::test_validate_infra_setup": 33.19675369400005, + "tests/aws/services/acm/test_acm.py::TestACM::test_boto_wait_for_certificate_validation": 1.1480961059999686, + "tests/aws/services/acm/test_acm.py::TestACM::test_certificate_for_subdomain_wildcard": 2.2637205599999675, + "tests/aws/services/acm/test_acm.py::TestACM::test_create_certificate_for_multiple_alternative_domains": 11.170948420999935, + "tests/aws/services/acm/test_acm.py::TestACM::test_domain_validation": 0.2811100949999741, + "tests/aws/services/acm/test_acm.py::TestACM::test_import_certificate": 0.9551102710000237, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiAuthorizer::test_authorizer_crud_no_api": 0.03310264200001711, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_doc_parts_crud_no_api": 0.03429111100001592, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_documentation_part_lifecycle": 0.07339201099995307, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_import_documentation_parts": 0.12221583699999883, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_create_documentation_part_operations": 0.03914029800006347, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_delete_documentation_part": 0.05047684400005892, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_part": 0.048526192999986506, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_get_documentation_parts": 0.01635896300007289, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiDocumentationPart::test_invalid_update_documentation_part": 0.05640672699996685, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_lifecycle": 0.08129479299998366, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_method_request_parameters": 0.05393583400001489, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_model": 0.2795849399999497, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_put_method_validation": 0.07090292199995929, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method": 0.07368372899998121, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiMethod::test_update_method_validation": 0.13818156000007775, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_lifecycle": 0.0751132460000008, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_model_validation": 0.10556115200006388, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiModels::test_update_model": 0.07314776200001916, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_create_request_validator_invalid_api_id": 0.016230218000032437, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_delete_request_validator": 0.044561748000035095, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validator": 0.04549244400004682, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_get_request_validators": 0.015654697000002216, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_invalid_update_request_validator_operations": 0.06543760299990709, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_request_validator_lifecycle": 0.09280407199997853, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRequestValidator::test_validators_crud_no_api": 0.0344503320000058, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource": 0.11954311300002018, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_proxy_resource_validation": 0.08098572400001558, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_create_resource_parent_invalid": 0.03206637800002454, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_delete_resource": 0.06822329500005253, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_resource_lifecycle": 0.10751375199998847, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiResource::test_update_resource_behaviour": 0.14247842299994318, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_private_type": 0.03133093999997527, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_verify_defaults": 0.0826050780000287, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_binary_media_types": 0.0253230080000435, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_endpoint_configuration": 0.10293876500003307, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_optional_params": 0.07566423599996597, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_create_rest_api_with_tags": 0.044168968999940716, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_get_api_case_insensitive": 0.0018951779999838436, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_list_and_delete_apis": 0.08739353700002539, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_behaviour": 0.05588219100002334, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_compression": 0.09430439800001977, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_concatenation_of_errors": 0.0018276529999639024, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_invalid_api_id": 0.015029675999983283, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_ip_address_type": 0.0774560000000406, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayApiRestApi::test_update_rest_api_operation_add_remove": 0.052793926999925134, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_crud": 0.09921318499999643, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_put": 0.09740967599998385, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_gateway_response_validation": 0.1005391929999746, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApiGatewayGatewayResponse::test_update_gateway_response": 0.12282829699995546, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_integration": 0.038563282000041, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_responsetemplates": 0.0018162520000259974, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_invalid_statuscode": 0.03856827800007068, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_api": 0.023568298000043342, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_method": 0.03877665999993951, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_resource": 0.037721541000053094, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_integration_response_wrong_status_code": 0.054336669999997866, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_integration_response": 0.10303518300003134, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_lifecycle_method_response": 0.13241593599991575, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_request_parameter_bool_type": 0.0018805020000058903, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_response_validation": 0.0733034350000139, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_put_integration_wrong_type": 0.04383381600001712, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_lack_response_parameters_and_models": 0.08486683399996764, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response": 0.09000289100009695, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_negative_tests": 0.10243967099995643, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_response_wrong_operations": 0.09888773099999071, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayIntegration::test_update_method_wrong_param_names": 0.0878184050000641, + "tests/aws/services/apigateway/test_apigateway_api.py::TestApigatewayTestInvoke::test_invoke_test_method": 0.1959305980000181, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_account": 0.044188597000072605, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_authorizer_crud": 0.003318288000002667, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_handle_domain_name": 0.2686420529999509, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_http_integration_with_path_request_parameter": 0.0019365969999967092, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_asynchronous_invocation": 1.3645322759999772, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_integration_aws_type": 7.716114429000015, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/foo1]": 0.0017481859999861626, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration[/lambda/{test_param1}]": 0.001681380999968951, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method": 0.0018055120000326497, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_any_method_with_path_param": 0.0017583930000455439, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_lambda_proxy_integration_with_is_base_64_encoded": 0.001733518000037293, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_gateway_mock_integration": 0.09330034399994247, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_api_mock_integration_response_params": 0.002424188000020422, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigateway_with_custom_authorization_method": 15.383287338999992, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[dev]": 1.6368445609999753, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_stage_variables[local]": 1.6171684699999673, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_apigw_test_invoke_method_api": 2.163376444999983, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping": 0.18347843499998362, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_base_path_mapping_root": 0.16499973100002308, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[host_based_url]": 0.06686188299994456, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[localstack_path_based_url]": 0.06711118600003374, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_create_rest_api_with_custom_id[path_based_url]": 0.07631847799996194, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_delete_rest_api_with_invalid_id": 0.015869297999984155, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.HOST_BASED]": 0.07175871400005462, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.LS_PATH_BASED]": 0.07421800399993117, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-False-UrlType.PATH_BASED]": 0.07279088000001366, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.HOST_BASED]": 0.09274283399997785, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.LS_PATH_BASED]": 0.06984821200001079, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://allowed-True-UrlType.PATH_BASED]": 0.07224105599999575, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.HOST_BASED]": 0.07533119999999371, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.LS_PATH_BASED]": 0.07377234199998384, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-False-UrlType.PATH_BASED]": 0.0739389249999931, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.HOST_BASED]": 0.06836662500006696, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.LS_PATH_BASED]": 0.07090400000004138, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_invoke_endpoint_cors_headers[http://denied-True-UrlType.PATH_BASED]": 0.06769686999990654, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_multiple_api_keys_validate": 0.4536940120000281, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_with_request_template": 0.0018380629999796838, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_put_integration_dynamodb_proxy_validation_without_request_template": 0.0018866439999669637, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_response_headers_invocation_with_apigw": 1.7460031090000143, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestAPIGateway::test_update_rest_api_deployment": 0.07651222699996651, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[custom]": 0.0019118400000479596, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_api_gateway_http_integrations[proxy]": 0.0017969859999311666, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-GET]": 0.09764421900013076, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.HOST_BASED-POST]": 0.0947300489999634, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-GET]": 0.09703258499985168, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[NEVER-UrlType.PATH_BASED-POST]": 0.09705641000005016, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-GET]": 0.09226495799998702, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.HOST_BASED-POST]": 0.09497064399999999, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-GET]": 0.10272432099975504, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_MATCH-UrlType.PATH_BASED-POST]": 0.09099922199993671, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-GET]": 0.0934383759999946, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.HOST_BASED-POST]": 0.09578710900007081, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-GET]": 0.09237763200007976, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestIntegrations::test_mock_integration_response[WHEN_NO_TEMPLATES-UrlType.PATH_BASED-POST]": 0.09429419200000666, + "tests/aws/services/apigateway/test_apigateway_basic.py::TestTagging::test_tag_api": 0.06429591200003415, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_apigw_call_api_with_aws_endpoint_url": 0.014597194999964813, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-ANY]": 4.171499312999799, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[UrlType.HOST_BASED-GET]": 3.35332452800003, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-ANY]": 3.3236510289999615, + "tests/aws/services/apigateway/test_apigateway_basic.py::test_rest_api_multi_region[path_based_url-GET]": 9.542072046000044, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestCanaryDeployments::test_invoking_canary_deployment": 0.13648489799993513, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment": 0.13926058100003047, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_by_stage_update": 0.13752940099993793, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_validation": 0.09695644300006734, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_canary_deployment_with_stage": 0.11710454900014611, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_create_update_stages": 0.14871556599996438, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_canary_deployment_validation": 0.15567285500003436, + "tests/aws/services/apigateway/test_apigateway_canary.py::TestStageCrudCanary::test_update_stage_with_copy_ops": 0.1486375260000159, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator": 2.425212325000075, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_models": 0.17476838099992165, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_api_gateway_request_validator_with_ref_one_ofmodels": 0.187592253000048, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_body_formatting": 3.5018493569998554, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_input_path_template_formatting": 0.6908128219998844, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_integration_request_parameters_mapping": 0.1136090749999994, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApiGatewayCommon::test_invocation_trace_id": 2.2869977689999814, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_api_not_existing": 0.02554235700006302, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_proxy_routing_with_hardcoded_resource_sibling": 0.20540643300012107, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_not_found": 0.11373807100005706, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_custom_api_id": 0.10049874500009537, + "tests/aws/services/apigateway/test_apigateway_common.py::TestApigatewayRouting::test_routing_with_hardcoded_resource_sibling_order": 0.19493483399992328, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[False]": 0.4204668679999486, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_delete_deployments[True]": 0.44987172600008307, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDeployments::test_create_update_deployments": 0.3295553340000197, + "tests/aws/services/apigateway/test_apigateway_common.py::TestDocumentations::test_documentation_parts_and_versions": 0.10952282300002025, + "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_create_update_stages": 0.3197226380000302, + "tests/aws/services/apigateway/test_apigateway_common.py::TestStages::test_update_stage_remove_wildcard": 0.3105788110000276, + "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_api_key_required_for_methods": 0.19925949699995726, + "tests/aws/services/apigateway/test_apigateway_common.py::TestUsagePlans::test_usage_plan_crud": 0.1838820330000317, + "tests/aws/services/apigateway/test_apigateway_custom_ids.py::test_apigateway_custom_ids": 0.06274015199994665, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_error_aws_proxy_not_supported": 0.15548442399983742, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[PutItem]": 0.3772713109999586, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Query]": 0.4541224700000157, + "tests/aws/services/apigateway/test_apigateway_dynamodb.py::test_rest_api_to_dynamodb_integration[Scan]": 0.3653261429999475, + "tests/aws/services/apigateway/test_apigateway_eventbridge.py::test_apigateway_to_eventbridge": 0.21300770900006682, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_api_keys": 0.1670736250000573, + "tests/aws/services/apigateway/test_apigateway_extended.py::TestApigatewayApiKeysCrud::test_get_usage_plan_api_keys": 0.16188638300002367, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_create_domain_names": 0.07956461499998113, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.39931950700008656, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_oas30_openapi[TEST_IMPORT_PETS]": 0.3111737780000112, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETSTORE_SWAGGER]": 0.4036765229999446, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_export_swagger_openapi[TEST_IMPORT_PETS]": 0.30411885499995606, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_name": 0.07746296599998459, + "tests/aws/services/apigateway/test_apigateway_extended.py::test_get_domain_names": 0.07705534800004443, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP]": 1.744569062000096, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_invoke_status_code_passthrough[HTTP_PROXY]": 1.7441883249999819, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP]": 2.0217669999999544, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_method[HTTP_PROXY]": 2.027997009999922, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP]": 2.1675972929999716, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_integration_with_lambda[HTTP_PROXY]": 2.173016971999914, + "tests/aws/services/apigateway/test_apigateway_http.py::test_http_proxy_integration_request_data_mappings": 1.948410329000012, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[openapi.spec.tf.json]": 0.35241164199999275, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_and_validate_rest_api[swagger-mock-cors.json]": 0.4365644740000789, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api": 0.06035900099993796, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[ignore]": 0.8657989819998875, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[prepend]": 0.8613788799999611, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_api_with_base_path_oas30[split]": 1.6964056360000086, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[ignore]": 0.5922471590000669, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[prepend]": 0.5977821230001155, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_rest_apis_with_base_path_swagger[split]": 0.591241810000156, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_swagger_api": 0.7460540279998895, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models": 0.3067615190000197, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_circular_models_and_request_validation": 0.4219246090000297, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_cognito_auth_identity_source": 0.3869235980000667, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_global_api_key_authorizer": 0.28459016299996165, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_http_method_integration": 0.2847467849999248, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_integer_http_status_code": 0.1743142950000447, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_import_with_stage_variables": 1.667605857999888, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[merge]": 0.33021948900011466, + "tests/aws/services/apigateway/test_apigateway_import.py::TestApiGatewayImportRestApi::test_put_rest_api_mode_binary_media_types[overwrite]": 0.32899698699986857, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS]": 2.3586747330000435, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_aws[AWS_PROXY]": 2.3890535940000746, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP]": 0.813288216999922, + "tests/aws/services/apigateway/test_apigateway_integrations.py::TestApiGatewayHeaderRemapping::test_apigateway_header_remapping_http[HTTP_PROXY]": 0.7782112009999764, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": 5.859094427000059, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_http_integration_status_code_selection": 0.11860772399995767, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_path_param": 0.096487512000067, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_request_overrides_in_response_template": 0.11882265999997799, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[False]": 0.08795569099982004, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_response_override_in_request_template[True]": 0.09037573399984922, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_vtl_map_assignation": 0.08944261299996015, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_response_with_response_template": 1.2012654609999345, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_responses": 0.167094815000155, + "tests/aws/services/apigateway/test_apigateway_integrations.py::test_put_integration_validation": 0.20484571099996174, + "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecord]": 1.1280290579999246, + "tests/aws/services/apigateway/test_apigateway_kinesis.py::test_apigateway_to_kinesis[PutRecords]": 1.0837325809999356, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_binary_response": 3.7181281240001454, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_aws_proxy_response_payload_format_validation": 4.0147401589999845, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration": 1.6763069419997692, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_response_with_mapping_templates": 1.9101436459999377, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_integration_with_request_template": 1.8046809319998829, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration": 4.020749378000005, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_non_post_method": 1.2620691260000285, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_integration_request_data_mapping": 2.7878649670000186, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_aws_proxy_response_format": 1.978934372000026, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_rust_proxy_integration": 1.7864217200000212, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_lambda_selection_patterns": 1.947849013999985, + "tests/aws/services/apigateway/test_apigateway_lambda.py::test_put_integration_aws_proxy_uri": 1.3054857660000607, + "tests/aws/services/apigateway/test_apigateway_lambda_cfn.py::TestApigatewayLambdaIntegration::test_scenario_validate_infra": 7.6370228880001605, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[CONVERT_TO_TEXT]": 0.5102879639999855, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request[None]": 0.5204568320000362, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary": 0.4612351429999535, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_request_convert_to_binary_with_request_template": 0.2910428669999874, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary": 0.5468044780000128, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_binary_with_request_template": 0.3460058929999832, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_convert_to_text": 0.5448694570000043, + "tests/aws/services/apigateway/test_apigateway_s3.py::TestApiGatewayS3BinarySupport::test_apigw_s3_binary_support_response_no_content_handling": 0.5332441679998965, + "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_any": 0.41948444400009066, + "tests/aws/services/apigateway/test_apigateway_s3.py::test_apigateway_s3_method_mapping": 0.4526789789999839, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_amz_json_protocol": 1.0394482770000195, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration": 1.2087063210000224, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttribute]": 0.25623724799993397, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_aws_integration_with_message_attribute[MessageAttributes]": 0.29208918999995603, + "tests/aws/services/apigateway/test_apigateway_sqs.py::test_sqs_request_and_response_xml_templates_integration": 0.3527486849999377, + "tests/aws/services/apigateway/test_apigateway_ssm.py::test_get_parameter_query_protocol": 0.0021656039999697896, + "tests/aws/services/apigateway/test_apigateway_ssm.py::test_ssm_aws_integration": 0.23231782100003784, + "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[DeleteStateMachine]": 1.3636021190001202, + "tests/aws/services/apigateway/test_apigateway_stepfunctions.py::TestApigatewayStepfunctions::test_apigateway_with_step_function_integration[StartExecution]": 1.467632612999978, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_api_exceptions": 0.0017169970000168178, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_exceptions": 0.0017518519999839555, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_create_invalid_desiredstate": 0.0017429450000463476, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_double_create_with_client_token": 0.0017755670000951795, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_lifecycle": 0.0020757259999300004, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources": 0.0017426330000489543, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_list_resources_with_resource_model": 0.001751651000063248, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceApi::test_update": 0.001815800999906969, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[FAIL]": 0.001712268000005679, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_edge_cases[SUCCESS]": 0.0017046940000682298, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_cancel_request": 0.0017367729999477888, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_get_request_status": 0.001746742000023005, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_invalid_request_token_exc": 0.0021492729999863514, + "tests/aws/services/cloudcontrol/test_cloudcontrol_api.py::TestCloudControlResourceRequestApi::test_list_request_status": 0.0017552089999526288, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_deleting_resource": 16.60300127800008, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_single_resource": 4.203500237000185, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": 4.242973699000004, + "tests/aws/services/cloudformation/api/test_changesets.py::test_autoexpand_capability_requirement": 0.08349290199998904, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_non_supported_resource_change_set": 17.41587952600014, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_remove_supported_resource_change_set": 19.685555488999967, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_and_then_update_refreshes_template_metadata": 2.153292856000121, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_create_existing": 1.0923951309999893, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_invalid_params": 0.01840552999999545, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_missing_stackname": 0.005579924000130632, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_nonexisting": 0.018098015000077794, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_update_without_parameters": 0.001843591000010747, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_with_ssm_parameter": 1.1907485520000591, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_change_set_without_parameters": 0.10083414700000048, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_changeset_with_stack_id": 0.24234561299999768, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_delete_create": 2.1557234859999426, + "tests/aws/services/cloudformation/api/test_changesets.py::test_create_while_in_review": 0.0019014209999568266, + "tests/aws/services/cloudformation/api/test_changesets.py::test_delete_change_set_exception": 0.028220685999940542, + "tests/aws/services/cloudformation/api/test_changesets.py::test_deleted_changeset": 0.04924295500006792, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_nonexisting": 0.02120703900004628, + "tests/aws/services/cloudformation/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": 0.05178600199997163, + "tests/aws/services/cloudformation/api/test_changesets.py::test_empty_changeset": 1.3380245959999684, + "tests/aws/services/cloudformation/api/test_changesets.py::test_execute_change_set": 0.002884323000102995, + "tests/aws/services/cloudformation/api/test_changesets.py::test_multiple_create_changeset": 0.34543644100006077, + "tests/aws/services/cloudformation/api/test_changesets.py::test_name_conflicts": 1.8864703020000206, + "tests/aws/services/cloudformation/api/test_drift_detection.py::test_drift_detection_on_lambda": 0.001905929000145079, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": 0.0017423829999643203, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": 0.0019202559999484947, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": 0.0018622180000420485, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": 0.0017235689999779424, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": 0.0017897820000598585, + "tests/aws/services/cloudformation/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": 0.0017213460000675695, + "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": 0.0018196979999629548, + "tests/aws/services/cloudformation/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": 0.0017710170000100334, + "tests/aws/services/cloudformation/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": 0.0018590509999967253, + "tests/aws/services/cloudformation/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": 0.0018084669999325342, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": 6.262731987000052, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_lifecycle_nested_stack": 0.002145435000102225, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_output_in_params": 12.620368036999935, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack": 6.216908944000011, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stack_output_refs": 6.2310139249999565, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_stacks_conditions": 6.229683845000068, + "tests/aws/services/cloudformation/api/test_nested_stacks.py::test_nested_with_nested_stack": 12.314458101000014, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": 2.1013337819998696, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": 2.1026105930000085, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_reference_unsupported_resource": 2.09764561399993, + "tests/aws/services/cloudformation/api/test_reference_resolving.py::test_sub_resolving": 2.1012158949999957, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_resource": 2.1050017269998307, + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_stack": 0.0017979980000291107, + "tests/aws/services/cloudformation/api/test_resources.py::test_invalid_logical_resource_id": 0.001857557999983328, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": 0.0016766500000358064, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": 0.0018233450000479934, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": 0.0019464429999516142, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": 0.0017952809999997044, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": 0.001691077000145924, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": 0.0017858450000858284, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": 0.0017097030000741142, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource0]": 0.001687539999920773, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_deletion[resource1]": 0.001701917999980651, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_modifying_with_policy_specifying_resource_id": 0.001723867999999129, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_replacement": 0.0017100820000450767, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": 0.001697819999890271, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": 0.001799168999923495, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::S3::Bucket]": 0.001808625999956348, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_prevent_update[AWS::SNS::Topic]": 0.0018231240001114202, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": 0.0017169949999242817, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": 0.0017714930000920504, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": 0.0018521880000434976, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": 0.0018131340000309137, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": 0.0017106640000292828, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_empty_policy": 0.001702617999967515, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[False]": 0.0018086659999880794, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_overlapping_policies[True]": 0.0018034770000667777, + "tests/aws/services/cloudformation/api/test_stack_policies.py::TestStackPolicy::test_update_with_policy": 0.0017121559999395686, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_create_stack_with_custom_id": 1.056034360000126, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[False-0]": 0.0018860420000237355, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_creation[True-1]": 0.0017333069999949657, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": 0.001718990000085796, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": 0.0017036610000786823, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": 2.1074827690000575, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": 2.1239871350001067, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": 1.0555241760000627, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": 1.0607874209999864, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": 2.1750061999998707, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": 2.2925931989999526, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": 4.360155511000016, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_name_creation": 0.07860215400000925, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_update_resources": 4.425764954000215, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_actual_update": 4.175067216999878, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": 2.107451615999935, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": 2.2610224899998457, + "tests/aws/services/cloudformation/api/test_stacks.py::test_blocked_stack_deletion": 0.002030843000056848, + "tests/aws/services/cloudformation/api/test_stacks.py::test_describe_stack_events_errors": 0.022973414999910347, + "tests/aws/services/cloudformation/api/test_stacks.py::test_events_resource_types": 2.1583805729999312, + "tests/aws/services/cloudformation/api/test_stacks.py::test_linting_error_during_creation": 0.0019213669999089689, + "tests/aws/services/cloudformation/api/test_stacks.py::test_list_parameter_type": 2.1137945210000453, + "tests/aws/services/cloudformation/api/test_stacks.py::test_name_conflicts": 2.386984763999976, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": 3.878301694000129, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_parameters_given": 0.0018081459999166327, + "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": 0.015929654999922604, + "tests/aws/services/cloudformation/api/test_stacks.py::test_notifications": 0.0017363819999900443, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[A-B-C]": 0.0016726640000115367, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[B-C]": 0.0017584629999873869, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[C]": 0.0018101599999909013, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-B-C]": 2.3761352669998814, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[A-C-B]": 3.356466095999963, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-A-C]": 2.3722535739999557, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[B-C-A]": 2.3766371900001104, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-A-B]": 2.3795422199998484, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order[C-B-A]": 2.3792353980001053, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_resource_not_found": 2.100299353000082, + "tests/aws/services/cloudformation/api/test_stacks.py::test_update_termination_protection": 2.1320911809999643, + "tests/aws/services/cloudformation/api/test_stacks.py::test_updating_an_updated_stack_sets_status": 6.375899655000012, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": 1.137744542000064, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": 0.08917710699995496, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": 1.1369696470001145, + "tests/aws/services/cloudformation/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": 0.09072440299996742, + "tests/aws/services/cloudformation/api/test_templates.py::test_get_template_summary": 2.2439851960000397, + "tests/aws/services/cloudformation/api/test_templates.py::test_validate_invalid_json_template_should_fail": 0.08895383399999446, + "tests/aws/services/cloudformation/api/test_templates.py::test_validate_template": 0.08847955199985336, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach": 1.28398543600008, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_multiple_resources": 1.371430903000146, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_foreach_use_case": 1.5426637510000774, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_length": 1.2642139150000276, + "tests/aws/services/cloudformation/api/test_transformers.py::TestLanguageExtensionsTransform::test_transform_to_json_string": 1.281880231999935, + "tests/aws/services/cloudformation/api/test_transformers.py::test_duplicate_resources": 2.3417493840000816, + "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_individual_resource_level": 2.236272604000078, + "tests/aws/services/cloudformation/api/test_transformers.py::test_transformer_property_level": 2.2799686430000747, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_basic_update": 3.125559779000014, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_diff_after_update": 3.1428604430001315, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_parameters_update": 3.125333459000103, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_no_template_error": 0.0017321149999816043, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_set_notification_arn_with_update": 0.0016914980000137803, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_tags": 0.0017482530000734187, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_using_template_url": 3.198716508999837, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability0]": 0.0017595950000668381, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_capabilities[capability1]": 0.0016987419999168196, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": 0.001676499999916814, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": 3.1298244789999217, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_template": 0.0020318829999723675, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_resource_types": 0.0017568590001246776, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_role_without_permissions": 0.001815127999861943, + "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_rollback_configuration": 0.0017118559999289573, + "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[missing-def]": 0.001763492000009137, + "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[multiple-nones]": 0.0017073289999416374, + "tests/aws/services/cloudformation/api/test_validations.py::test_invalid_output_structure[none-value]": 0.0018114820001073895, + "tests/aws/services/cloudformation/api/test_validations.py::test_missing_resources_block": 0.0018176739999944402, + "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[invalid-key]": 0.0016819109999914872, + "tests/aws/services/cloudformation/api/test_validations.py::test_resources_blocks[missing-type]": 0.002281871000036517, + "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": 2.11394815999995, + "tests/aws/services/cloudformation/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": 0.0017644649999510875, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_condition_on_outputs": 2.121150009999951, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[create]": 2.137761912999963, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_att_to_conditional_resources[no-create]": 2.1230121830000144, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[dev-us-west-2]": 2.1170115820001456, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_in_conditional[production-us-east-1]": 2.1214601240000093, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_conditional_with_select": 2.110122431999912, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[None-FallbackParamValue]": 2.1341245180000215, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[false-DefaultParamValue]": 2.132010191000063, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependency_in_non_evaluated_if_branch[true-FallbackParamValue]": 2.1303397129998984, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": 0.001959299000077408, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_intrinsic_fn_condition": 0.0017425549999643408, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref_with_macro": 0.0018605129999968995, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": 0.001820147000103134, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": 0.0018001209999738421, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": 0.0017225070001813947, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": 0.001705295000078877, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": 0.0016584869998723661, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_deploys_resource": 2.1052150109999275, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_condition_evaluation_doesnt_deploy_resource": 0.08353427300016847, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[nope]": 2.0967086540000537, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_simple_intrinsic_fn_condition_evaluation[yep]": 2.0908001850000346, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_sub_in_conditions": 2.125380936000056, + "tests/aws/services/cloudformation/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": 4.273278978000121, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level": 2.0760344390000682, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level_v2": 0.0018052220000299712, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level": 2.074849825000001, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level_v2": 0.0017394079999348833, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": 2.0970130059999974, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": 0.001691558999937115, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": 0.0016807680000283653, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": 2.115749810000011, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": 2.106206555999961, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": 0.0016777920000095037, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": 0.0017848820000381238, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_simple_mapping_working": 2.118823743000007, + "tests/aws/services/cloudformation/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": 0.001937156999929357, + "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_fn_sub_cases": 2.1165338979999433, + "tests/aws/services/cloudformation/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": 2.1105821689999402, + "tests/aws/services/cloudformation/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": 2.1261828630000537, + "tests/aws/services/cloudformation/engine/test_references.py::test_useful_error_when_invalid_ref": 0.01702250300002106, + "tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/test_basic.py::TestBasicCRD::test_black_box": 2.586013567000009, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_instance_with_key_pair": 2.4016557190000185, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_prefix_list": 7.204716917000155, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_security_group_with_tags": 2.1093914660001474, + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_vpc_endpoint": 2.5060077169999886, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_autogenerated_values": 2.1049612350001325, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_black_box": 2.1395344940000314, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_getatt": 2.143277921000049, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestUpdates::test_update_without_replacement": 0.0018192369999496805, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Arn]": 0.0016847250000182612, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Id]": 0.0017141209999635976, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[Path]": 0.0017066169999679914, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[PermissionsBoundary]": 0.001682712000047104, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_exploration.py::TestAttributeAccess::test_getatt[UserName]": 0.001727184999936071, + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py::TestParity::test_create_with_full_properties": 2.2541321510000216, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_cfn_handle_iam_role_resource_no_role_name": 2.1331989489999614, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_delete_role_detaches_role_policy": 4.219865868000056, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_user_access_key": 4.203700863999984, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_iam_username_defaultname": 2.172978223999962, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": 2.4628631289999703, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_policy_attachments": 2.308611945999928, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": 2.2375893020000603, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_update_inline_policy": 4.272767767999994, + "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_updating_stack_with_iam_role": 12.252599989999908, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Arn]": 0.0016456530000823477, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainArn]": 0.0016508219999877838, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainEndpoint]": 0.0017410420000487647, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[DomainName]": 0.0018362090002028708, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[EngineVersion]": 0.0017026289999648725, + "tests/aws/services/cloudformation/resource_providers/opensearch/test_domain.py::TestAttributeAccess::test_getattr[Id]": 0.001797225000018443, + "tests/aws/services/cloudformation/resource_providers/scheduler/test_scheduler.py::test_schedule_and_group": 2.4954773119999345, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestBasicCRD::test_black_box": 0.0019168599999375147, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter.py::TestUpdates::test_update_without_replacement": 0.0017883389999724386, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[AllowedPattern]": 0.0016922099999874263, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[DataType]": 0.0018516479999561852, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Description]": 0.001674657000080515, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Id]": 0.0016954259999693022, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Name]": 0.0017442869999513277, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Policies]": 0.0016956859999481821, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Tier]": 0.0016846060000261787, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Type]": 0.0018814040000734167, + "tests/aws/services/cloudformation/resource_providers/ssm/test_parameter_getatt_exploration.py::TestAttributeAccess::test_getattr[Value]": 0.0017140510000217546, + "tests/aws/services/cloudformation/resources/test_acm.py::test_cfn_acm_certificate": 2.1028550560000667, + "tests/aws/services/cloudformation/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": 11.473736782000287, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_account": 2.1578160290000596, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": 2.1152817780000532, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_apigateway_deployment_canary_settings": 2.2562682550001227, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_aws_integration": 2.3089156689998163, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_rest_api": 2.2957339309999725, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_apigateway_swagger_import": 2.3262323140000944, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": 2.4564516899998807, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": 2.226214439999808, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": 2.299121481000043, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_cfn_with_apigateway_resources": 2.3572097729999086, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": 9.93769836499996, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_apigateway_stage": 4.550822254000082, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_update_usage_plan": 4.475145669000085, + "tests/aws/services/cloudformation/resources/test_apigateway.py::test_url_output": 2.187530018000075, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": 8.637806173000172, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": 8.638476824999998, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": 8.651323603000264, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[28]": 8.64418076600009, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v20]": 0.0022973989998718025, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v28]": 0.0018395950000922312, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": 2.431017688999873, + "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_create_macro": 3.2041652070001874, + "tests/aws/services/cloudformation/resources/test_cloudformation.py::test_waitcondition": 2.2019821690000754, + "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_creation": 2.0958300160002636, + "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_alarm_ext_statistic": 2.122915504000048, + "tests/aws/services/cloudformation/resources/test_cloudwatch.py::test_composite_alarm_creation": 2.4002466909996656, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": 2.5141953689999355, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": 4.590627526999924, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_default_name_for_table": 2.4537159080000492, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_deploy_stack_with_dynamodb_table": 2.2323047780000707, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table": 2.489518433000285, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": 2.165245140000252, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_globalindex_read_write_provisioned_throughput_dynamodb_table": 2.1851400720001948, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_table_with_ttl_and_sse": 2.1569535360001737, + "tests/aws/services/cloudformation/resources/test_dynamodb.py::test_ttl_cdk": 1.2747237590001532, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_update_ec2_instance_type": 0.001818896999793651, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": 2.4735556329999326, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_cfn_with_multiple_route_tables": 2.211510575000375, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_dhcp_options": 2.312844975000189, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": 2.1573278610001125, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_internet_gateway_ref_and_attr": 2.2897037440000076, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_keypair_create_import": 2.232498845999771, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation": 2.2313987809998252, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": 2.2311862259998634, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_transit_gateway_attachment": 2.771779110000125, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_creates_default_sg": 2.395330137999963, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_gateway_attachment": 2.135810845000151, + "tests/aws/services/cloudformation/resources/test_ec2.py::test_vpc_with_route_table": 2.3014839850000044, + "tests/aws/services/cloudformation/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": 4.115462190999779, + "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_api_destination_resource": 16.394034288000057, + "tests/aws/services/cloudformation/resources/test_events.py::test_cfn_event_bus_resource": 2.146866438999723, + "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_creation_without_target": 2.1116420790001484, + "tests/aws/services/cloudformation/resources/test_events.py::test_event_rule_to_logs": 2.219282454999984, + "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policies": 21.4056699260002, + "tests/aws/services/cloudformation/resources/test_events.py::test_eventbus_policy_statement": 2.105404742000019, + "tests/aws/services/cloudformation/resources/test_events.py::test_rule_pattern_transformation": 2.128788450000002, + "tests/aws/services/cloudformation/resources/test_events.py::test_rule_properties": 2.142607321000014, + "tests/aws/services/cloudformation/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": 33.62043496000001, + "tests/aws/services/cloudformation/resources/test_integration.py::test_events_sqs_sns_lambda": 13.602896320000127, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": 11.39437064599997, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_default_parameters_kinesis": 11.325008727000068, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_describe_template": 0.13282050699990577, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": 11.352896459999783, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_kinesis_stream_consumer_creations": 17.29064326899993, + "tests/aws/services/cloudformation/resources/test_kinesis.py::test_stream_creation": 11.352983344999984, + "tests/aws/services/cloudformation/resources/test_kms.py::test_cfn_with_kms_resources": 2.134395283999993, + "tests/aws/services/cloudformation/resources/test_kms.py::test_deploy_stack_with_kms": 2.1161368419998325, + "tests/aws/services/cloudformation/resources/test_kms.py::test_kms_key_disabled": 2.112407564999785, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": 15.59948700999962, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": 10.710541444999762, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": 19.301275056000122, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": 7.85210617000007, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": 10.176085011000168, + "tests/aws/services/cloudformation/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": 9.432694003999814, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_cfn_function_url": 7.524305479999839, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_event_invoke_config": 6.268788497999822, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_alias": 12.51579330200002, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": 11.092673582000089, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run": 6.5559191440002, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_empty_string_replacement_deny_list": 8.28124392299992, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_cfn_run_with_non_empty_string_replacement_deny_list": 10.285128483000108, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_code_signing_config": 2.186850039000092, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_function_tags": 6.547051750000037, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_layer_crud": 6.26397325899984, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_logging_config": 6.222256460999688, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version": 6.771435309000026, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": 12.548022002999915, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_vpc": 0.0019845649999297166, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter": 11.457332871000062, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": 12.704720802999645, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": 6.197278712000298, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": 6.640897741999879, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function": 8.292781436000041, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_function_name": 12.33258359599995, + "tests/aws/services/cloudformation/resources/test_lambda.py::test_update_lambda_permissions": 10.304740804000403, + "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": 2.3897777870001846, + "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": 2.1164149449998604, + "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain": 0.0017248219999146386, + "tests/aws/services/cloudformation/resources/test_opensearch.py::test_domain_with_alternative_types": 23.30926568699988, + "tests/aws/services/cloudformation/resources/test_redshift.py::test_redshift_cluster": 2.126371152000047, + "tests/aws/services/cloudformation/resources/test_resource_groups.py::test_group_defaults": 2.254084568999815, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_health_check": 2.246439781999925, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_id": 2.184016272000008, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_via_name": 2.1729717629998504, + "tests/aws/services/cloudformation/resources/test_route53.py::test_create_record_set_without_resource_record": 2.1663038229999074, + "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_autoname": 2.1062908409996908, + "tests/aws/services/cloudformation/resources/test_s3.py::test_bucket_versioning": 2.111595578999868, + "tests/aws/services/cloudformation/resources/test_s3.py::test_bucketpolicy": 25.00248885299993, + "tests/aws/services/cloudformation/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": 2.1648365959999865, + "tests/aws/services/cloudformation/resources/test_s3.py::test_cors_configuration": 2.5013530239998545, + "tests/aws/services/cloudformation/resources/test_s3.py::test_object_lock_configuration": 2.4992607720000706, + "tests/aws/services/cloudformation/resources/test_s3.py::test_website_configuration": 2.4726493149998987, + "tests/aws/services/cloudformation/resources/test_sam.py::test_cfn_handle_serverless_api_resource": 6.618440815999975, + "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_policies": 6.317094359000066, + "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_sqs_event": 13.47793883300028, + "tests/aws/services/cloudformation/resources/test_sam.py::test_sam_template": 6.614902843999744, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": 1.2645608639998045, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": 2.2760648179998952, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": 2.115438253999855, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": 2.121947516000091, + "tests/aws/services/cloudformation/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": 2.2653890609997234, + "tests/aws/services/cloudformation/resources/test_sns.py::test_deploy_stack_with_sns_topic": 2.1341063759998633, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription": 2.1247478469999805, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_subscription_region": 2.1511419230000683, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": 2.3312352490002013, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_fifo_without_suffix_fails": 2.0890751590000036, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_policy_resets_to_default": 1.3637455650000447, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_attributes": 4.463646967999921, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_update_name": 4.4903038470001775, + "tests/aws/services/cloudformation/resources/test_sns.py::test_sns_topic_with_attributes": 1.2244381599998633, + "tests/aws/services/cloudformation/resources/test_sns.py::test_update_subscription": 4.239389170999857, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_cfn_handle_sqs_resource": 2.146579284000154, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": 2.1132954610002344, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": 2.117536552999809, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_sqs_queue_policy": 2.1260813569999755, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_queue_no_change": 4.230693784999858, + "tests/aws/services/cloudformation/resources/test_sqs.py::test_update_sqs_queuepolicy": 4.2204190999998445, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_deploy_patch_baseline": 2.2624110960000507, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_maintenance_window": 2.1853208979998726, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_parameter_defaults": 2.2971127749999596, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameter_tag": 4.201823373000025, + "tests/aws/services/cloudformation/resources/test_ssm.py::test_update_ssm_parameters": 4.192905553999935, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": 2.3928167750000284, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_delete_nonexistent_stack_set": 0.0017121869998391048, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": 0.0016586560000177997, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke": 9.585257728999977, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost": 9.589155032999997, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_localhost_with_path": 15.68982486599998, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_apigateway_invoke_with_path": 15.686948264000193, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": 4.808067015000233, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_cfn_statemachine_with_dependencies": 2.180428647000099, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_nested_statemachine_with_sync2": 15.497937807000199, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_retry_and_catch": 0.002750855000158481, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": 2.669256025000095, + "tests/aws/services/cloudformation/resources/test_stepfunctions.py::test_statemachine_definitionsubstitution": 7.319085831999928, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": 0.0016923900000165304, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": 0.0016858279998359649, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": 0.0017079290003039205, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": 0.0019102569999631669, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": 0.0017389860001912893, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": 0.0017331960000319668, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": 0.0016628149999178277, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": 0.0017077069999231753, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import": 0.0018361579998327215, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export": 0.001765596999803165, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export_then_create": 0.001787255999943227, + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": 0.0017948219999652792, + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": 0.0017414410001492797, + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": 0.001780134000000544, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": 0.0019121099999210855, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": 0.0017773579997992783, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": 0.002100392000102147, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": 0.0016811690002214164, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": 0.0016317570000410342, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": 0.00167333499985034, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": 0.0016920290001962712, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": 0.0017900620000546041, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": 0.0018054619999929855, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": 0.0017919949998486118, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": 0.0017507189998013928, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": 0.001645751999831191, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": 0.001663475999748698, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": 0.0016450109999368578, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": 0.0017381640000166954, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": 0.0017636229999880015, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": 0.0017726389999097591, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": 0.0017338979998839932, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": 0.0016285700000935321, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": 0.0016256960000191611, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": 0.0016248940003151802, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": 0.0016503400001965929, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": 0.0016261660000509437, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": 0.0016277390000141168, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": 0.001624904999971477, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": 0.0016117679999752, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": 0.0018227539999315923, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": 0.001676679999945918, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": 0.0016405629999098892, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": 0.0017500669998753438, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": 0.0016186819998438295, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": 0.0016452620000109164, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": 0.0017224770001575962, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[false]": 0.0017228470001100504, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[true]": 0.0017492160000074364, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[json]": 0.0016850770000473858, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[yml]": 0.0016938129999743978, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_fn_transform": 0.0019436890001998108, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_for_attribute_fn_transform": 0.0017671489999884216, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[json]": 0.0016694869998445938, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[yml]": 0.0016500799999903393, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_macro_fn_transform": 0.0016519639998477942, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_macro_with_intrinsic_function": 0.0017130590001670498, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_multiple_fn_transform_order": 0.0017515209999601211, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_remove_transform_in_update_change_set": 0.0017624599997816404, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_serverless_fn_transform": 0.0016459119999581162, + "tests/aws/services/cloudformation/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_update_parameter_transform_in_update_change_set": 0.0017866670000330487, + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": 0.001907091000020955, + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": 0.0016397720000895788, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": 0.0017521019999549026, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": 0.0018909199998233817, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": 0.0016594070000337524, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": 0.0017496370001026662, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": 0.0016128310001022328, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": 0.0016145940001024428, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": 0.0019632859998637286, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": 0.001729850999709015, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": 0.0018222720000267145, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": 0.0017830379999850265, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": 0.001723048000030758, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": 0.0017378150000695314, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": 0.0017415420002180326, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": 0.0024578290001500136, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": 0.0017615490000935097, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": 0.0018993060000411788, + "tests/aws/services/cloudformation/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": 0.0017389960000855353, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": 0.0019093850000899693, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": 0.0016270870000880677, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": 0.0016976189999695634, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": 0.0016549599997688347, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": 0.0018391549999705603, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": 0.0018422800001189898, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": 0.0020282969999243505, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": 0.0016715419999400183, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": 0.0016285599999719125, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": 0.0016477369999847724, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": 0.0016057280001859908, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": 0.0016482580001593305, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_single_resource_static_update": 0.0016830810002375074, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": 0.0019515829999363632, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": 0.0025444519999382464, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup": 0.0016681739998603007, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup_no_change": 0.0016874619998361595, + "tests/aws/services/cloudformation/test_cloudformation_ui.py::TestCloudFormationUi::test_get_cloudformation_ui": 0.07087177499988684, + "tests/aws/services/cloudformation/test_cloudtrail_trace.py::test_cloudtrail_trace_example": 0.0017144620003364253, + "tests/aws/services/cloudformation/test_list_stacks.py::test_listing_stacks": 0.001711736000061137, + "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_cfn_with_exports": 2.129703407000079, + "tests/aws/services/cloudformation/test_template_engine.py::TestImportValues::test_import_values_across_stacks": 4.218429638999851, + "tests/aws/services/cloudformation/test_template_engine.py::TestImports::test_stack_imports": 4.238023765999742, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-0-False]": 0.09907959299994218, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-0-1-False]": 0.0983928220002781, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-0-False]": 0.08971150300021691, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::And-1-1-True]": 2.1325790909997977, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-0-False]": 0.09873249299971576, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-0-1-True]": 2.134128249000014, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-0-True]": 2.1253795869997703, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_and_or_functions[Fn::Or-1-1-True]": 2.1220411090000653, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_base64_sub_and_getatt_functions": 2.1085004649999064, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": 2.109741786999848, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cidr_function": 0.0017781199999262753, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_find_map_function": 2.107121879000033, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": 2.11716289900005, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": 2.119510653000134, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": 2.1191863980000107, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": 2.122952092000105, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": 2.107145602999708, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": 2.1171561580001708, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": 2.115281915000196, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": 2.124439433999896, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": 2.1118574139998145, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_split_length_and_join_functions": 2.178685537999854, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_not_ready": 2.127630968999938, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": 2.112956637000252, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_to_json_functions": 0.0018142960000204766, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_attribute_uses_macro": 5.770135154000172, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_capabilities_requirements": 5.312949566000043, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": 0.02842929100006586, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": 3.712356016000058, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": 3.693857016000038, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": 3.6816982199998165, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": 3.6942343330001677, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": 4.704120663000367, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_global_scope": 5.141002163999701, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_macro_deployment": 3.216067997999744, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_pyplate_param_type_list": 8.781060088000004, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_scope_order_and_parameters": 0.002169821000052252, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": 5.745485516999906, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": 5.7824336879998555, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": 3.768539604000125, + "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_validate_lambda_internals": 5.253064706999794, + "tests/aws/services/cloudformation/test_template_engine.py::TestPreviousValues::test_parameter_usepreviousvalue_behavior": 0.0021946320000552078, + "tests/aws/services/cloudformation/test_template_engine.py::TestPseudoParameters::test_stack_id": 2.1092648269998335, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager.yaml]": 2.1339338519999274, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_full.yaml]": 2.1307523149998815, + "tests/aws/services/cloudformation/test_template_engine.py::TestSecretsManagerParameters::test_resolve_secretsmanager[resolve_secretsmanager_partial.yaml]": 2.122234507999792, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": 2.176874660000294, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": 2.1807570010000745, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm": 2.137861382999745, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_missing_parameter": 0.0018432009999287402, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_secure": 2.14168872800019, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_with_version": 2.1685947030000534, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": 6.255056018999767, + "tests/aws/services/cloudformation/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": 2.387715266000214, + "tests/aws/services/cloudformation/test_template_engine.py::TestTypes::test_implicit_type_conversion": 2.162425242999916, + "tests/aws/services/cloudformation/test_unsupported.py::test_unsupported": 2.101188609000019, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_change_parameter_type": 0.0016797260000203096, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_update_parameter_between_deployments": 0.0018094089998612617, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_alarm_lambda_target": 2.9755068900003607, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_anomaly_detector_lifecycle": 0.0017434159999538679, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_aws_sqs_metrics_created": 2.4135925830000815, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_breaching_alarm_actions": 5.3358392619998085, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_create_metric_stream": 0.0017903920002027007, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_dashboard_lifecycle": 0.1464766920000784, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_default_ordering": 0.1308530209998935, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_delete_alarm": 0.09959845499997755, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_alarms_converts_date_format_correctly": 0.07559760399999504, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_describe_minimal_metric_alarm": 0.08490833999985625, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_enable_disable_alarm_actions": 10.287214994999886, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data": 2.0725063500001397, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data0]": 0.0017846819998794672, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data1]": 0.0018076139999720908, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_different_units_no_unit_in_query[metric_data2]": 0.0018387840000286815, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_for_multiple_metrics": 1.0604760020000867, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_pagination": 2.1990231970000877, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Average]": 0.03580178600009276, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Maximum]": 0.03535426700022981, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Minimum]": 0.034640267999748175, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[SampleCount]": 0.03439412600005198, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_stats[Sum]": 0.03505325600008291, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_different_units": 0.028882187999897724, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_dimensions": 0.04439132800007428, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_data_with_zero_and_labels": 0.04197668300002988, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_statistics": 0.18506900499983203, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_no_results": 0.06609895000019606, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_get_metric_with_null_dimensions": 0.03772109900000942, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_handle_different_units": 0.0346131159997185, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_insight_rule": 0.002024860999881639, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_amount_of_datapoints": 0.5856164940000781, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_invalid_dashboard_name": 0.018958002999852397, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs0]": 0.03661946400006855, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs1]": 0.03496823599994059, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs2]": 0.03527518600026269, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs3]": 0.03702134200011642, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs4]": 0.03686697800003458, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs5]": 0.037177468999743724, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_label_generation[input_pairs6]": 0.03846491100011917, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_pagination": 5.39233045900005, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_uniqueness": 2.0627343930002553, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_list_metrics_with_filters": 4.082997005000152, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_metric_widget": 0.0016607310001290898, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions": 2.11719610199998, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_multiple_dimensions_statistics": 0.06290475300011167, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_parallel_put_metric_data_list_metrics": 0.25421318599978804, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_composite_alarm_describe_alarms": 0.09896581999987575, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm": 10.641603677000148, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_alarm_escape_character": 0.07883860500010087, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_gzip": 0.0243919160002406, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_validation": 0.04673240400006762, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_data_values_list": 0.033433424000122614, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_put_metric_uses_utc": 0.03152765999971052, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_raw_metric_data": 0.025436218999857374, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm": 2.35290423299989, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_set_alarm_invalid_input": 0.09113918599996396, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_store_tags": 0.12490251900021576, + "tests/aws/services/cloudwatch/test_cloudwatch.py::TestCloudwatch::test_trigger_composite_alarm": 4.661622572999704, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_error": 2.5794736290001765, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestCloudWatchLambdaMetrics::test_lambda_invoke_successful": 2.5245264130001033, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSQSMetrics::test_alarm_number_of_messages_sent": 61.314633013000275, + "tests/aws/services/cloudwatch/test_cloudwatch_metrics.py::TestSqsApproximateMetrics::test_sqs_approximate_metrics": 26.612144517999923, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_binary": 0.11868001399989225, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items": 0.1217536790001077, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_items_streaming": 2.5240124979999337, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_existing_table": 0.2034014279998928, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_batch_write_not_matching_schema": 0.16346118299998125, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_binary_data_with_stream": 1.2782128339999872, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_continuous_backup_update": 0.661466634999897, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_create_duplicate_table": 0.1305932609999445, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_data_encoding_consistency": 2.0086208479998504, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_delete_table": 0.1481724339998891, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_batch_execute_statement": 0.28039995100004944, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_class": 0.6269344870001987, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_partial_sse_specification": 0.28792869400001564, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_create_table_with_sse_specification": 0.0783718840000347, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_statement_empy_parameter": 0.13588737100008075, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_execute_transaction": 0.4791199459999689, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_get_batch_items": 0.14357033900012084, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_idempotent_writing": 0.2465973139998141, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_partiql_missing": 0.16088194000008116, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_pay_per_request": 0.047224905999996736, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_records_with_update_item": 0.003432643999985885, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_shard_iterator": 5.81003364899982, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_stream_stream_view_type": 2.6381817269998464, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_describe_with_exclusive_start_shard_id": 1.0554268829999955, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_streams_shard_iterator_format": 3.3278635430001486, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_update_table_without_sse_specification_change": 0.1373292539999511, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_dynamodb_with_kinesis_stream": 1.5006449410000187, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_empty_and_binary_values": 0.11472903200046858, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables": 0.13853395299997828, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_global_tables_version_2019": 0.6086760820001018, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PAY_PER_REQUEST]": 0.7177930720000631, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_gsi_with_billing_mode[PROVISIONED]": 0.5342810350000491, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_invalid_query_index": 0.0950653500003682, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_kinesis_streaming_destination_crud": 0.5600695489999907, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_large_data_download": 0.44861940200007666, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_list_tags_of_resource": 0.09407117299952006, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_more_than_20_global_secondary_indexes": 0.3159692319995884, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_multiple_update_expressions": 0.16616543599957367, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_non_ascii_chars": 0.15895687400006864, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_nosql_workbench_localhost_region": 0.07930044399995495, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_query_on_deleted_resource": 0.20167921199993089, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_in_put_item": 0.15172418500014828, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_return_values_on_conditions_check_failure": 0.3126191700001755, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_stream_destination_records": 14.353858462999938, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_streams_on_global_tables": 1.5058907799999588, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live": 0.30154063000009046, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_time_to_live_deletion": 0.49163837300011437, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_get_items": 0.13744075199986128, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming": 1.7566703409999036, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transact_write_items_streaming_for_different_tables": 1.4584616619999906, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_binary_data": 0.1382428549999304, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_canceled": 0.14563191699994604, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_transaction_write_items": 0.175173467000036, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_local_secondary_index": 0.14956228099981672, + "tests/aws/services/dynamodb/test_dynamodb.py::TestDynamoDB::test_valid_query_index": 0.09264761699978408, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_enable_kinesis_streaming_destination": 0.003940192999948522, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_non_existent_stream": 0.02208411000003707, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_stream_spec_and_region_replacement": 2.4692396970001482, + "tests/aws/services/dynamodbstreams/test_dynamodb_streams.py::TestDynamoDBStreams::test_table_v2_stream": 3.9607591369998545, + "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3": 0.7834961549999662, + "tests/aws/services/ec2/test_ec2.py::TestEc2FlowLogs::test_ec2_flow_logs_s3_validation": 0.26482862000000296, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_route_table_association": 0.8250066479999987, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-id_manager]": 0.10111270400000194, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[False-tag]": 0.10496487099987917, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-id_manager]": 0.08089584499998637, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_security_group_with_custom_id[True-tag]": 0.33546448199979295, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id": 0.05682795900008841, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_custom_id_and_vpc_id": 0.06757846199991491, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_subnet_with_tags": 0.12480234800011658, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_endpoint": 0.1704457440000624, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_create_vpc_with_custom_id": 0.0960083920000443, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpc_endpoints_with_filter": 0.6348287879999361, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_describe_vpn_gateways_filter_by_vpc": 0.6375245049999876, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_get_security_groups_for_vpc": 0.6451590369999849, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[id]": 0.8789611059999061, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_modify_launch_template[name]": 0.05313740099995812, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_reserved_instance_api": 0.04332004200011852, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vcp_peering_difference_regions": 1.3770180320001373, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vpc_endpoint_dns_names": 0.5896649099998967, + "tests/aws/services/ec2/test_ec2.py::test_create_specific_vpc_id": 0.025931824000053894, + "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_ids": 0.29882184200005213, + "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_names": 0.2963775599998826, + "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filters": 0.3120324939999364, + "tests/aws/services/ec2/test_ec2.py::test_pickle_ec2_backend": 2.3819519310000032, + "tests/aws/services/ec2/test_ec2.py::test_raise_create_volume_without_size": 0.01701216399999339, + "tests/aws/services/ec2/test_ec2.py::test_raise_duplicate_launch_template_name": 0.035312943999997515, + "tests/aws/services/ec2/test_ec2.py::test_raise_invalid_launch_template_name": 0.012800616000049558, + "tests/aws/services/ec2/test_ec2.py::test_raise_modify_to_invalid_default_version": 0.041395351999881314, + "tests/aws/services/ec2/test_ec2.py::test_raise_when_launch_template_data_missing": 0.01605376999998498, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_domain": 0.0016163770000048316, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_create_existing_domain_causes_exception": 0.0016443580000213842, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_describe_domains": 0.001777706999973816, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_domain_version": 0.0017298190000474278, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_version_for_domain": 0.0016237199998840879, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_get_compatible_versions": 0.0016053949999559336, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_list_versions": 0.0016678320000664826, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_path_endpoint_strategy": 0.0017104719998997098, + "tests/aws/services/es/test_es.py::TestElasticsearchProvider::test_update_domain_config": 0.001633847999983118, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth0]": 0.13804265099997792, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth1]": 0.09194900199986478, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_api_destinations[auth2]": 0.09259715000007418, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_invalid_parameters": 0.01406771100005244, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeApiDestinations::test_create_api_destination_name_validation": 0.04371504899995671, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[api-key]": 0.055549948000134464, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[basic]": 0.054287788999999975, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_connection_secrets[oauth]": 0.05543477299988808, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection": 0.047473330000002534, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_invalid_parameters": 0.013929122000035932, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_name_validation": 0.01418861799993465, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params0]": 0.04529888199988363, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params1]": 0.04634848800014879, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_create_connection_with_auth[auth_params2]": 0.047522759000003134, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_delete_connection": 0.09820032300001458, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_list_connections": 0.04683429499982594, + "tests/aws/services/events/test_api_destinations_and_connection.py::TestEventBridgeConnections::test_update_connection": 0.09025843599999916, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[custom]": 0.08603570799994031, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_duplicate[default]": 0.05778979199999412, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_archive_error_unknown_event_bus": 0.013917066999965755, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[custom]": 0.11573028599980262, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_create_list_describe_update_delete_archive[default]": 0.09177417400007926, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_delete_archive_error_unknown_archive": 0.013253147999989778, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_describe_archive_error_unknown_archive": 0.013501127999916207, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_error_unknown_source_arn": 0.013040832999990926, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[custom]": 0.087399333999997, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_state_enabled[default]": 0.0593224829999599, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-custom]": 0.5378723530000116, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[False-default]": 0.5126213730001155, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-custom]": 0.6218176589999302, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_events[True-default]": 0.7564964469997904, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[custom]": 0.10135928900001545, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_name_prefix[default]": 0.07383759999993345, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[custom]": 0.08605405899993457, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_list_archive_with_source_arn[default]": 0.05878266299998813, + "tests/aws/services/events/test_archive_and_replay.py::TestArchive::test_update_archive_error_unknown_archive": 0.0016482859999769062, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_describe_replay_error_unknown_replay": 0.014658639999993284, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replay_with_limit": 0.214904152000031, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_event_source_arn": 0.09885816199994224, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_list_replays_with_prefix": 0.15020619799997803, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[custom]": 0.0016814269999940734, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_list_describe_canceled_replay[default]": 0.0016296100000090519, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_different_archive": 0.11986713600003895, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_duplicate_name_same_archive": 0.06900280900003963, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[0]": 0.06305418499994175, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_invalid_end_time[10]": 0.06411326900001768, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_archive": 0.013774411000099462, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::test_start_replay_error_unknown_event_bus": 0.09118114200009586, + "tests/aws/services/events/test_archive_and_replay.py::TestReplay::tests_concurrency_error_too_many_active_replays": 0.0018629350000765044, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions0]": 0.04119572499996593, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[False-regions1]": 0.11031399099999817, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions0]": 0.04497561699997732, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_list_describe_delete_custom_event_buses[True-regions1]": 0.12821673300004477, + "tests/aws/services/events/test_events.py::TestEventBus::test_create_multiple_event_buses_same_name": 0.04195589400012523, + "tests/aws/services/events/test_events.py::TestEventBus::test_delete_default_event_bus": 0.01262865499995769, + "tests/aws/services/events/test_events.py::TestEventBus::test_describe_delete_not_existing_event_bus": 0.02124205300003723, + "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_limit": 0.22460056299985354, + "tests/aws/services/events/test_events.py::TestEventBus::test_list_event_buses_with_prefix": 0.0777941329999976, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[domain]": 0.27777721300003577, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[path]": 0.2797369999998409, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_bus_to_bus[standard]": 0.42732108000006974, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_nonexistent_event_bus": 0.1576303250001274, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_events_to_default_eventbus_for_custom_eventbus": 1.0207264650000525, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[custom]": 0.27854532600008497, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission[default]": 0.08950696699992022, + "tests/aws/services/events/test_events.py::TestEventBus::test_put_permission_non_existing_event_bus": 0.013374555000154942, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[custom]": 0.08258493999994698, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission[default]": 0.061687439000024824, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-custom]": 0.04169417400009934, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[False-default]": 0.021041150999963065, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-custom]": 0.046749348000048485, + "tests/aws/services/events/test_events.py::TestEventBus::test_remove_permission_non_existing_sid[True-default]": 0.027749998000103915, + "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_nested": 10.229166803999988, + "tests/aws/services/events/test_events.py::TestEventPattern::test_put_events_pattern_with_values_in_array": 5.283187468999927, + "tests/aws/services/events/test_events.py::TestEventRule::test_delete_rule_with_targets": 0.06966644800002086, + "tests/aws/services/events/test_events.py::TestEventRule::test_describe_nonexistent_rule": 0.014998986999898989, + "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[custom]": 0.08715812300010839, + "tests/aws/services/events/test_events.py::TestEventRule::test_disable_re_enable_rule[default]": 0.05904793300010169, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[custom]": 0.21492031199989015, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target[default]": 0.15598865699996622, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[custom]": 0.1192882399999462, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_no_matches[default]": 0.09172276299989335, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[custom]": 0.28890574799982005, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_names_by_target_with_limit[default]": 0.2569044480001139, + "tests/aws/services/events/test_events.py::TestEventRule::test_list_rule_with_limit": 0.21942469500015704, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_pattern_to_single_matching_rules_single_target": 7.350472242000137, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_different_targets": 0.5021784040001194, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_multiple_matching_rules_single_target": 18.61240980499997, + "tests/aws/services/events/test_events.py::TestEventRule::test_process_to_single_matching_rules_single_target": 10.408956167999918, + "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[custom]": 0.08102293299998564, + "tests/aws/services/events/test_events.py::TestEventRule::test_put_list_with_prefix_describe_delete_rule[default]": 0.05227214199999253, + "tests/aws/services/events/test_events.py::TestEventRule::test_put_multiple_rules_with_same_name": 0.07959964599990599, + "tests/aws/services/events/test_events.py::TestEventRule::test_update_rule_with_targets": 0.0907388489999903, + "tests/aws/services/events/test_events.py::TestEventTarget::test_add_exceed_fife_targets_per_rule": 0.0906461299999819, + "tests/aws/services/events/test_events.py::TestEventTarget::test_list_target_by_rule_limit": 0.14256736100014678, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[custom]": 0.10316171499994198, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_list_remove_target[default]": 0.07676138099998298, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_across_different_rules": 0.10711562499989213, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_arn_single_rule": 0.07653039500007708, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_across_different_rules": 0.10929169199994249, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_multiple_targets_with_same_id_single_rule": 0.07514029100002517, + "tests/aws/services/events/test_events.py::TestEventTarget::test_put_target_id_validation": 0.08835769999996046, + "tests/aws/services/events/test_events.py::TestEvents::test_create_connection_validations": 0.013610148000111622, + "tests/aws/services/events/test_events.py::TestEvents::test_events_written_to_disk_are_timestamp_prefixed_for_chronological_ordering": 0.001673312999855625, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[ARRAY]": 0.01371632200005024, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[MALFORMED_JSON]": 0.013395112999887715, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[SERIALIZED_STRING]": 0.01335113100003582, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_malformed_detail[STRING]": 0.013771436000070025, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_with_too_big_detail": 0.019602104999989933, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail": 0.013143292999984624, + "tests/aws/services/events/test_events.py::TestEvents::test_put_event_without_detail_type": 0.013639758999943297, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[custom]": 0.04388925799992194, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_exceed_limit_ten_entries[default]": 0.016613137999911487, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_response_entries_order": 0.2915971500001433, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_time": 0.3009040310000728, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_target_delivery_failure": 1.1537356920000548, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_with_time_field": 0.18543271599992295, + "tests/aws/services/events/test_events.py::TestEvents::test_put_events_without_source": 0.013695233999897027, + "tests/aws/services/events/test_events_cross_account_region.py::TestEventsCrossAccountRegion::test_put_events[custom-account]": 0.16275260099996558, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-account]": 0.43034267399991677, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region]": 0.43148328699987815, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[custom-region_account]": 0.43461282600003415, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-account]": 0.4747130040001366, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region]": 0.4686556019997852, + "tests/aws/services/events/test_events_cross_account_region.py::test_event_bus_to_event_bus_cross_account_region[default-region_account]": 0.46650848299998415, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path": 0.18532657400010066, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_max_level_depth": 0.18142169500004002, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_multiple_targets": 0.28472736200001236, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail0]": 0.187054799999828, + "tests/aws/services/events/test_events_inputs.py::TestInputPath::test_put_events_with_input_path_nested[event_detail1]": 0.18331991299999117, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" multiple list items\"]": 0.22611754599995493, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item multiple list items system account id payload user id\"]": 0.22864668299996538, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\" single list item\"]": 0.22679264999999305, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[\"Payload of with path users-service/users/ and \"]": 0.23209244100019077, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : \"\"}]": 0.22659582500000397, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"id\" : }]": 0.2331136029999925, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"nested\": {\"level1\": {\"level2\": {\"level3\": \"users-service/users/\"} } }, \"bod\": \"\"}]": 0.22809976200005622, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": }]": 0.2244991980001032, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"bod\": [, \"hardcoded\"]}]": 0.22708781300002556, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"method\": \"PUT\", \"path\": \"users-service/users/\", \"id\": , \"body\": }]": 0.2280643639999198, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"multi_replacement\": \"users//second/\"}]": 0.23077327200007858, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement[{\"singlelistitem\": }]": 0.22617590899994866, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"not_valid\": \"users-service/users/\", \"bod\": }]": 5.151313044000062, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"payload\": \"\"}]": 5.153470328000026, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_nested_keys_replacement_not_valid[{\"singlelistitem\": \"\"}]": 5.1527390430001105, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[\"Message containing all pre defined variables \"]": 0.21319953499994426, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_input_transformer_predefined_variables[{\"originalEvent\": , \"originalEventJson\": }]": 0.2124191760000258, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_json": 0.40145161000009466, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"Event of type, at time , info extracted from detail \"]": 0.39990414900000815, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_input_template_string[\"{[/Check with special starting characters for event of type\"]": 0.4086548299998185, + "tests/aws/services/events/test_events_inputs.py::TestInputTransformer::test_put_events_with_input_transformer_missing_keys": 0.1114619689999472, + "tests/aws/services/events/test_events_inputs.py::test_put_event_input_path_and_input_transformer": 0.09879232299988416, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_array_event_payload": 0.013714947999801552, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays]": 0.015009980999934669, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_NEG]": 0.013842887000009796, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_EXC]": 0.08702242399999705, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[arrays_empty_null_NEG]": 0.014824409000084415, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean]": 0.0141180410000743, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[boolean_NEG]": 0.014506860000210509, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_many_rules]": 0.01515423799992277, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match]": 0.01478579600006924, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_multi_match_NEG]": 0.01385989800007792, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or]": 0.014362676000018837, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[complex_or_NEG]": 0.013696404000143048, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase]": 0.01357307699993271, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_EXC]": 0.08584350099999938, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_NEG]": 0.014078643999937412, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list]": 0.01480962499999805, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_EXC]": 0.08651182699998117, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_ignorecase_list_NEG]": 0.01368670700014718, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number]": 0.013855337000109103, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_NEG]": 0.013619370999890634, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list]": 0.013952699000014945, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_list_NEG]": 0.013996150000025409, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_number_zero]": 0.01431970300006924, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string]": 0.013814598000067235, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_NEG]": 0.014658793999842601, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list]": 0.015172298000038609, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_list_NEG]": 0.014037085000040861, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_but_string_null]": 0.014371011999969596, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix]": 0.014414669999951002, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_NEG]": 0.014031939999881615, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_empty_EXC]": 0.08516360399994483, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_ignorecase_EXC]": 0.0855278440001257, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_int_EXC]": 0.08488301899990347, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list]": 0.015155083000081504, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_NEG]": 0.014003450000018347, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_prefix_list_type_EXC]": 0.08745131100010894, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix]": 0.013869046999843704, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_NEG]": 0.014534857999933593, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_empty_EXC]": 0.08654657900001439, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_ignorecase_EXC]": 0.08563686600018627, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_int_EXC]": 0.08901042100001177, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list]": 0.014467919000026086, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_NEG]": 0.01383183600000848, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_suffix_list_type_EXC]": 0.08685673299999053, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard]": 0.013932318000115629, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_NEG]": 0.014393073999940498, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_empty]": 0.014136435000068559, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list]": 0.01411239599997316, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_NEG]": 0.014008701000079782, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_list_type_EXC]": 0.0871573519998492, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_anything_wildcard_type_EXC]": 0.09189497299996674, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists]": 0.01417937200005781, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_NEG]": 0.013591408000024785, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false]": 0.014106529999935447, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_exists_false_NEG]": 0.014092727000047489, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase]": 0.013524804999974549, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_EXC]": 0.08531353300008959, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_NEG]": 0.014157744000044659, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty]": 0.014441700000020319, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_empty_NEG]": 0.014008271999955468, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ignorecase_list_EXC]": 0.08679955700006303, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address]": 0.015173254000160341, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_EXC]": 0.0867639790000112, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_NEG]": 0.013781329000039477, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_ip_EXC]": 0.08971550699993713, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_bad_mask_EXC]": 0.08566964399994959, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_type_EXC]": 0.08818649100010134, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6]": 0.013745474999950602, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_NEG]": 0.013694998000005398, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_ip_address_v6_bad_ip_EXC]": 0.08667680899998231, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_EXC]": 0.08704824300002656, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and]": 0.014464544999896134, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_and_NEG]": 0.013825901999894086, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_number_EXC]": 0.0850742770001034, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_operatorcasing_EXC]": 0.08662674500010326, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_numeric_syntax_EXC]": 0.09694260299988855, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix]": 0.01378582400002415, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_NEG]": 0.013621399999919959, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_empty]": 0.014253280000161794, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_ignorecase]": 0.013936352000087027, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_int_EXC]": 0.08573810600000797, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_prefix_list_EXC]": 0.08752954900012355, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix]": 0.014274511999929018, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_NEG]": 0.013876779999918654, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_empty]": 0.014378697999973156, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase]": 0.01351134899994122, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_ignorecase_NEG]": 0.014636425000048803, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_int_EXC]": 0.08642790500005049, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_suffix_list_EXC]": 0.08679319500015481, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_complex_EXC]": 0.08791886699998486, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_empty_NEG]": 0.013878212000122403, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_int_EXC]": 0.0881848330000139, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_list_EXC]": 0.0874982299999374, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating]": 0.014266884999983631, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_nonrepeating_NEG]": 0.013890255999967849, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating]": 0.014426635999939208, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_NEG]": 0.013810686999931931, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_repeating_star_EXC]": 0.0851028160000169, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[content_wildcard_simplified]": 0.013911318000054962, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event]": 0.013827634000108446, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_event_NEG]": 0.0161037520000491, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern]": 0.014079656000035357, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dot_joining_pattern_NEG]": 0.013970126000003802, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[dynamodb]": 0.013818529999980456, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb]": 0.014714180000055421, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_dynamodb_NEG]": 0.014277246000006016, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[exists_list_empty_NEG]": 0.015070195000021158, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[int_nolist_EXC]": 0.08785013600004277, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[key_case_sensitive_NEG]": 0.013844812000002094, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[list_within_dict]": 0.013955265000049621, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[minimal]": 0.013704953999990721, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[nested_json_NEG]": 0.013967098999955851, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value]": 0.015003452999962974, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[null_value_NEG]": 0.013663603000054536, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[number_comparison_float]": 0.015154116000076101, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-int-float]": 0.013930718999972669, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-null_NEG]": 0.015330022000057397, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[numeric-string_NEG]": 0.013977889999978288, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_case_sensitive_EXC]": 0.08663995199992769, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[operator_multiple_list]": 0.013885787999925014, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-anything-but]": 0.01388722299998335, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists-parent]": 0.014162522999981775, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-exists]": 0.013668632999838337, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but]": 0.013866571000107797, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[or-numeric-anything-but_NEG]": 0.01372930499985614, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[prefix]": 0.013812443000119856, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[sample1]": 0.013957004000076267, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string]": 0.013648162000094999, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_empty]": 0.014129642999932912, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern[string_nolist_EXC]": 0.08639153099989016, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_source": 0.02253614599987941, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_escape_characters": 0.01182434999986981, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_pattern_with_multi_key": 0.011843937999969967, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_event_with_large_and_complex_payload": 0.03464734200008479, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_event_payload": 0.013927378000062163, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[[\"not\", \"a\", \"dict\", \"but valid json\"]]": 0.0871154320000187, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[this is valid json but not a dict]": 0.08666880399982801, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{\"not\": closed mark\"]": 0.08540457299989157, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_invalid_json_event_pattern[{'bad': 'quotation'}]": 0.08609180500002367, + "tests/aws/services/events/test_events_patterns.py::TestEventPattern::test_plain_string_payload": 0.013943955999934587, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_event_with_content_base_rule_in_pattern": 0.18938287800006037, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_anything_but": 5.304934322999998, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_false": 5.240080464000016, + "tests/aws/services/events/test_events_patterns.py::TestRuleWithPattern::test_put_events_with_rule_pattern_exists_true": 5.23484009799995, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::test_schedule_cron_target_sqs": 0.0016840709999996761, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 1 * * * *)]": 0.014004742999986775, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(0 dummy ? * MON-FRI *)]": 0.013618901000086225, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(7 20 * * NOT *)]": 0.013568750999979784, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(71 8 1 * ? *)]": 0.013653745999931743, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_invalid_schedule_cron[cron(INVALID)]": 0.013300094999976864, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(* * ? * SAT#3 *)]": 0.03792857400003413, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 10 * * ? *)]": 0.037292580999974234, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 12 * * ? *)]": 0.03628429299999425, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 18 ? * MON-FRI *)]": 0.03715216399984911, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT *)]": 0.037111846000129844, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 2 ? * SAT#3 *)]": 0.03751797599989004, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0 8 1 * ? *)]": 0.037052050000056624, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/10 * ? * MON-FRI *)]": 0.036762034999924253, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/15 * * * ? *)]": 0.03787962600006267, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 0-2 ? * MON-FRI *)]": 0.037367445999848314, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/30 20-23 ? * MON-FRI *)]": 0.0371274630000471, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 5 ? JAN 1-5 2022)]": 0.03744052100000772, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(0/5 8-17 ? * MON-FRI *)]": 0.036917909999829135, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 10 ? * 6L 2002-2005)]": 0.03538028900015888, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(15 12 * * ? *)]": 0.03706055600014224, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_put_rule_with_schedule_cron[cron(5,35 14 * * ? *)]": 0.0357257190000837, + "tests/aws/services/events/test_events_schedule.py::TestScheduleCron::tests_scheduled_rule_does_not_trigger_on_put_events": 3.094357278000075, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[ rate(10 minutes)]": 0.011156593000009707, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate( 10 minutes )]": 0.011320026999896982, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate()]": 0.011297677000015938, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(-10 minutes)]": 0.011196148000067296, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(0 minutes)]": 0.011525322000011329, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 days)]": 0.012052332999928694, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 hours)]": 0.011305370999934894, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(1 minutes)]": 0.011451822999902106, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 MINUTES)]": 0.01112807000004068, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 day)]": 0.011161773000026187, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 hour)]": 0.011530741000001399, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minute)]": 0.01129434000006313, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 minutess)]": 0.01138633499988373, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 seconds)]": 0.011905359000024873, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10 years)]": 0.011019094999937806, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(10)]": 0.011125485999968987, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_invalid_schedule_rate[rate(foo minutes)]": 0.011159288999920136, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_put_rule_with_schedule_rate": 0.037407454999993206, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::test_scheduled_rule_logs": 0.001721441000086088, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_put_rule_with_schedule_custom_event_bus": 0.04127352399996198, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_custom_input_target_sqs": 60.11261915499995, + "tests/aws/services/events/test_events_schedule.py::TestScheduleRate::tests_schedule_rate_target_sqs": 0.0015946849999863844, + "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_create_event_bus_with_tags": 0.0439939590000904, + "tests/aws/services/events/test_events_tags.py::TestEventBusTags::test_list_tags_for_deleted_event_bus": 0.03509144300005573, + "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_list_tags_for_deleted_rule": 0.06445893799991609, + "tests/aws/services/events/test_events_tags.py::TestRuleTags::test_put_rule_with_tags": 0.06611210399989886, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_custom]": 0.06781218699984493, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[event_bus-event_bus_default]": 0.0200973429999749, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_custom]": 0.08962444799999503, + "tests/aws/services/events/test_events_tags.py::test_recreate_tagged_resource_without_tags[rule-event_bus_default]": 0.06300641899986203, + "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_event_bus]": 0.028693987999986348, + "tests/aws/services/events/test_events_tags.py::tests_tag_list_untag_not_existing_resource[not_existing_rule]": 0.030072727000060695, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_custom]": 0.07282386400015639, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[event_bus-event_bus_default]": 0.04815450299997792, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_custom]": 0.09550481700011915, + "tests/aws/services/events/test_events_tags.py::tests_tag_untag_resource[rule-event_bus_default]": 0.06673516699993343, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth0]": 0.11668117400006395, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth1]": 0.10715313200000764, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiDestination::test_put_events_to_target_api_destinations[auth2]": 0.11205456900006538, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetApiGateway::test_put_events_with_target_api_gateway": 8.982435020999901, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetCloudWatchLogs::test_put_events_with_target_cloudwatch_logs": 0.2043077060000087, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination0]": 0.28658511499986616, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination1]": 0.31414409599994997, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetEvents::test_put_events_with_target_events[bus_combination2]": 0.2884542319999355, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetFirehose::test_put_events_with_target_firehose": 0.9809467290000384, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetKinesis::test_put_events_with_target_kinesis": 0.8870009050000363, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda": 4.238156874000083, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entries_partial_match": 4.252539584000033, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetLambda::test_put_events_with_target_lambda_list_entry": 4.273031046000142, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[domain]": 0.2253765039999962, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[path]": 0.22506159299996398, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSns::test_put_events_with_target_sns[standard]": 0.5282233109999197, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs": 0.18136374600021554, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetSqs::test_put_events_with_target_sqs_event_detail_match": 5.212575001999994, + "tests/aws/services/events/test_events_targets.py::TestEventsTargetStepFunctions::test_put_events_with_target_statefunction_machine": 2.0040739870000834, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_api_gateway": 5.734543566999946, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination0]": 4.368328792999819, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination1]": 4.402403422000134, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_events[bus_combination2]": 4.3725094730000365, + "tests/aws/services/events/test_x_ray_trace_propagation.py::test_xray_trace_propagation_events_lambda": 4.255353292999871, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_elasticsearch_s3_backup": 0.001886918999844056, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source": 33.27712145799978, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_kinesis_as_source_multiple_delivery_streams": 59.06128245400009, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[domain]": 0.0018466039998656925, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[path]": 0.0018332080001073336, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_opensearch_s3_backup[port]": 0.0018601479998778814, + "tests/aws/services/firehose/test_firehose.py::TestFirehoseIntegration::test_kinesis_firehose_s3_as_destination_with_file_extension": 1.1767361210002036, + "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[False]": 0.07907121300013387, + "tests/aws/services/firehose/test_firehose.py::test_kinesis_firehose_http[True]": 1.5638133879999714, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_role_with_malformed_assume_role_policy_document": 0.020668365000119593, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_add_permission_boundary_afterwards": 0.11342273699983707, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_create_user_with_permission_boundary": 0.10082096400014962, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_role": 0.12242186000025868, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_root": 0.04118589300014719, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_get_user_without_username_as_user": 0.15296501600028023, + "tests/aws/services/iam/test_iam.py::TestIAMExtensions::test_role_with_path_lifecycle": 0.12089077600012388, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_detach_role_policy": 0.08296502899997904, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_attach_iam_role_to_new_iam_user": 0.0984593519997361, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_describe_role": 0.14652888999989955, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_role_with_assume_role_policy": 0.13029336499994315, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_create_user_with_tags": 0.03338742500022818, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_delete_non_existent_policy_returns_no_such_entity": 0.015164812000193706, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_instance_profile_tags": 0.1482598180000423, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_list_roles_with_permission_boundary": 0.168882677000056, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_recreate_iam_role": 0.05999805900000865, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_role_attach_policy": 0.40644747699980144, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[ecs.amazonaws.com-AWSServiceRoleForECS]": 0.0018824799997219088, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_service_linked_role_name_should_match_aws[eks.amazonaws.com-AWSServiceRoleForAmazonEKS]": 0.0017065420001927123, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[group]": 0.18640959200001816, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[role]": 0.21509650200005126, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_simulate_principle_policy[user]": 0.22333949599988046, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_update_assume_role_policy": 0.11601781000013034, + "tests/aws/services/iam/test_iam.py::TestIAMIntegrations::test_user_attach_policy": 0.4260523909999847, + "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_group_policy_encoding": 0.060505212999714786, + "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_role_policy_encoding": 0.17688589999988835, + "tests/aws/services/iam/test_iam.py::TestIAMPolicyEncoding::test_put_user_policy_encoding": 0.0814635219999218, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_already_exists": 0.035790165999742385, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_deletion": 10.007560959999637, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[accountdiscovery.ssm.amazonaws.com]": 0.277296879000005, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[acm.amazonaws.com]": 0.26900826399992184, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[appmesh.amazonaws.com]": 0.27106666200006657, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling-plans.amazonaws.com]": 0.27077739500009557, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[autoscaling.amazonaws.com]": 0.2740396660001352, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[backup.amazonaws.com]": 0.27470203899997614, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[batch.amazonaws.com]": 1.2090845479999643, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cassandra.application-autoscaling.amazonaws.com]": 0.27146476499979144, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cks.kms.amazonaws.com]": 0.2715647810000519, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[cloudtrail.amazonaws.com]": 0.27695300000004863, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[codestar-notifications.amazonaws.com]": 0.27713155099991127, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[config.amazonaws.com]": 0.2877000710000175, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[connect.amazonaws.com]": 0.3141826069997933, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms-fleet-advisor.amazonaws.com]": 0.2702447619999475, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[dms.amazonaws.com]": 0.2747667000001002, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[docdb-elastic.amazonaws.com]": 0.27299487799996314, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2-instance-connect.amazonaws.com]": 0.27880606600001556, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ec2.application-autoscaling.amazonaws.com]": 0.27545078599996486, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecr.amazonaws.com]": 0.27555257800008803, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ecs.amazonaws.com]": 0.275760124000044, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-connector.amazonaws.com]": 0.27582929400000467, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-fargate.amazonaws.com]": 0.27182284199989226, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks-nodegroup.amazonaws.com]": 0.27420590700012326, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[eks.amazonaws.com]": 0.2745032099999207, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticache.amazonaws.com]": 0.2774113230000239, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticbeanstalk.amazonaws.com]": 0.2770733640002163, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticfilesystem.amazonaws.com]": 0.27515516599987677, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[elasticloadbalancing.amazonaws.com]": 0.2750420289999056, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[email.cognito-idp.amazonaws.com]": 0.2752807819999816, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emr-containers.amazonaws.com]": 0.27836150799998904, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[emrwal.amazonaws.com]": 0.2733927809999841, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[fis.amazonaws.com]": 0.2749227150000024, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[grafana.amazonaws.com]": 0.2724066320001839, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[imagebuilder.amazonaws.com]": 0.2780650249999326, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[iotmanagedintegrations.amazonaws.com]": 0.3433751719999236, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafka.amazonaws.com]": 0.2725292710001668, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[kafkaconnect.amazonaws.com]": 0.2699560420001035, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lakeformation.amazonaws.com]": 0.2738988149999386, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lex.amazonaws.com]": 0.34637628100017537, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lexv2.amazonaws.com]": 0.27159360299970103, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[lightsail.amazonaws.com]": 0.27191399600019395, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[m2.amazonaws.com]": 0.27349803800007066, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[memorydb.amazonaws.com]": 0.27368671699991864, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mq.amazonaws.com]": 0.27409968999995726, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[mrk.kms.amazonaws.com]": 0.2736939750000147, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[notifications.amazonaws.com]": 0.27242325800011713, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[observability.aoss.amazonaws.com]": 0.27527996099979646, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opensearchservice.amazonaws.com]": 0.27666655900020487, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.apigateway.amazonaws.com]": 0.2813423170000533, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ops.emr-serverless.amazonaws.com]": 0.29131322400007775, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsdatasync.ssm.amazonaws.com]": 0.29403591300024345, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[opsinsights.ssm.amazonaws.com]": 0.271443655999974, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[pullthroughcache.ecr.amazonaws.com]": 0.2738982060000126, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ram.amazonaws.com]": 0.2722880090000217, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rds.amazonaws.com]": 0.2711616330000197, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[redshift.amazonaws.com]": 0.2745554099999481, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.cassandra.amazonaws.com]": 0.27366717499990045, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[replication.ecr.amazonaws.com]": 0.27304710199996407, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[repository.sync.codeconnections.amazonaws.com]": 0.2725298450000082, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[resource-explorer-2.amazonaws.com]": 0.2718563509999967, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[rolesanywhere.amazonaws.com]": 0.27377991899993503, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[s3-outposts.amazonaws.com]": 0.27829778300019825, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ses.amazonaws.com]": 1.3523740870002712, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[shield.amazonaws.com]": 0.2737279709997438, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-incidents.amazonaws.com]": 0.2738587750000079, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm-quicksetup.amazonaws.com]": 0.27283026300005986, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[ssm.amazonaws.com]": 0.2725522370001272, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[sso.amazonaws.com]": 0.2724592140000368, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[vpcorigin.cloudfront.amazonaws.com]": 0.27424899200013897, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[waf.amazonaws.com]": 0.27465672199991786, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle[wafv2.amazonaws.com]": 0.27452632799963794, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[autoscaling.amazonaws.com]": 0.1331661039998835, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[connect.amazonaws.com]": 0.13294247399994674, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix[lexv2.amazonaws.com]": 0.13095380800018575, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[accountdiscovery.ssm.amazonaws.com]": 0.016345075999879555, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[acm.amazonaws.com]": 0.015726800000038565, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[appmesh.amazonaws.com]": 0.01598159500008478, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[autoscaling-plans.amazonaws.com]": 0.015039929000067787, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[backup.amazonaws.com]": 0.015603673999976309, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[batch.amazonaws.com]": 0.016722065000067232, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cassandra.application-autoscaling.amazonaws.com]": 0.016561507000005804, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cks.kms.amazonaws.com]": 0.015489616000195383, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[cloudtrail.amazonaws.com]": 0.0166454539996721, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[codestar-notifications.amazonaws.com]": 0.01579118799986645, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[config.amazonaws.com]": 0.017205106000119486, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms-fleet-advisor.amazonaws.com]": 0.015548110000054294, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[dms.amazonaws.com]": 0.017461000999901444, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[docdb-elastic.amazonaws.com]": 0.016087705999780155, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2-instance-connect.amazonaws.com]": 0.015337131999785925, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ec2.application-autoscaling.amazonaws.com]": 0.015723303999948257, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecr.amazonaws.com]": 0.016055501999971966, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ecs.amazonaws.com]": 0.015790106999702402, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-connector.amazonaws.com]": 0.01564002600002823, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-fargate.amazonaws.com]": 0.01732503999983237, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks-nodegroup.amazonaws.com]": 0.015077276999818423, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[eks.amazonaws.com]": 0.015585745999715073, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticache.amazonaws.com]": 0.01730791699992551, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticbeanstalk.amazonaws.com]": 0.01536206800005857, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticfilesystem.amazonaws.com]": 0.015076886999850103, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[elasticloadbalancing.amazonaws.com]": 0.017352661000131775, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[email.cognito-idp.amazonaws.com]": 0.016958365999926173, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emr-containers.amazonaws.com]": 0.01565487600009874, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[emrwal.amazonaws.com]": 0.015745132999882117, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[fis.amazonaws.com]": 0.01673363699978836, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[grafana.amazonaws.com]": 0.015811847999884776, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[imagebuilder.amazonaws.com]": 0.015526927999871987, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[iotmanagedintegrations.amazonaws.com]": 0.016856615000051534, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafka.amazonaws.com]": 0.015810942999678446, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[kafkaconnect.amazonaws.com]": 0.016275752999717952, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lakeformation.amazonaws.com]": 0.015072818999897208, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lex.amazonaws.com]": 0.01622623000025669, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[lightsail.amazonaws.com]": 0.016486505999864676, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[m2.amazonaws.com]": 0.015000525000004927, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[memorydb.amazonaws.com]": 0.015855169000133174, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mq.amazonaws.com]": 0.015352690000099756, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[mrk.kms.amazonaws.com]": 0.015747683999961737, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[notifications.amazonaws.com]": 0.015747066999892922, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[observability.aoss.amazonaws.com]": 0.01669025700016391, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opensearchservice.amazonaws.com]": 0.016330323999909524, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.apigateway.amazonaws.com]": 0.016153485000131695, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ops.emr-serverless.amazonaws.com]": 0.015361447999794109, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsdatasync.ssm.amazonaws.com]": 0.015570016999845393, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[opsinsights.ssm.amazonaws.com]": 0.01544915099998434, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[pullthroughcache.ecr.amazonaws.com]": 0.016469872000243413, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ram.amazonaws.com]": 0.015714028000502367, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rds.amazonaws.com]": 0.016601150000042253, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[redshift.amazonaws.com]": 0.015745585000104256, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.cassandra.amazonaws.com]": 0.016895568000336425, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[replication.ecr.amazonaws.com]": 0.015525582999998733, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[repository.sync.codeconnections.amazonaws.com]": 0.016151642999830074, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[resource-explorer-2.amazonaws.com]": 0.015535402000068643, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[rolesanywhere.amazonaws.com]": 0.015920640000331332, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[s3-outposts.amazonaws.com]": 0.016967623000027743, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ses.amazonaws.com]": 0.01568954999993366, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[shield.amazonaws.com]": 0.016177982000272095, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-incidents.amazonaws.com]": 0.015962345999923855, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm-quicksetup.amazonaws.com]": 0.01680478999992374, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[ssm.amazonaws.com]": 0.016535517000193067, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[sso.amazonaws.com]": 0.016212444000075266, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[vpcorigin.cloudfront.amazonaws.com]": 0.015308137999682003, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[waf.amazonaws.com]": 0.01724219700008689, + "tests/aws/services/iam/test_iam.py::TestIAMServiceRoles::test_service_role_lifecycle_custom_suffix_not_allowed[wafv2.amazonaws.com]": 0.01696005899998454, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_service": 0.0799473100000796, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_create_service_specific_credential_invalid_user": 0.02543489200024851, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_delete_user_after_service_credential_created": 0.07981998100012788, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_id_match_user_mismatch": 0.09851836000007097, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_invalid_update_parameters": 0.07785333899983016, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_list_service_specific_credential_different_service": 0.08103368499996577, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[cassandra.amazonaws.com]": 0.106088722000095, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_service_specific_credential_lifecycle[codecommit.amazonaws.com]": 0.11771435800028485, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[satisfiesregexbutstillinvalid]": 0.09712550400013242, + "tests/aws/services/iam/test_iam.py::TestIAMServiceSpecificCredentials::test_user_match_id_mismatch[totally-wrong-credential-id-with-hyphens]": 0.09618197600002532, + "tests/aws/services/iam/test_iam.py::TestRoles::test_role_with_tags": 0.07934382500025094, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_add_tags_to_stream": 0.6769533990002401, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_cbor_blob_handling": 0.6750276639997992, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_shard_count": 0.6753286729999672, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_create_stream_without_stream_name_raises": 0.04612744999985807, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records": 0.7486385219999647, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_empty_stream": 0.6792337660001522, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_next_shard_iterator": 0.6716255310000179, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_get_records_shard_iterator_with_surrounding_quotes": 0.669554097000173, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_record_lifecycle_data_integrity": 0.8825893909997831, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_stream_consumers": 1.3488987580001321, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard": 4.553263033000121, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_cbor_at_timestamp": 4.408774803000142, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_timeout": 6.359311443999786, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp": 4.525376155000004, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_at_timestamp_cbor": 0.649644760000001, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesis::test_subscribe_to_shard_with_sequence_number_as_iterator": 1.7220093099999758, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisJavaSDK::test_subscribe_to_shard_with_java_sdk_v2_lambda": 9.657286587999806, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_add_tags_to_stream": 0.6955843530001857, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_cbor_blob_handling": 0.6743041480001466, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_shard_count": 0.6563690650000353, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_create_stream_without_stream_name_raises": 0.04562705399985134, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records": 0.7425783580004008, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_empty_stream": 0.6815097649998734, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_next_shard_iterator": 0.6818538630000148, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_get_records_shard_iterator_with_surrounding_quotes": 0.6804103859997213, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_record_lifecycle_data_integrity": 0.8832939010001155, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_stream_consumers": 1.2945204720001584, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard": 1.6525463710001986, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_cbor_at_timestamp": 4.388678252999853, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_timeout": 6.338300532999938, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp": 4.536929928000291, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_at_timestamp_cbor": 0.6528376950000165, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisMockScala::test_subscribe_to_shard_with_sequence_number_as_iterator": 4.598155706999933, + "tests/aws/services/kinesis/test_kinesis.py::TestKinesisPythonClient::test_run_kcl": 30.208785199999966, + "tests/aws/services/kms/test_kms.py::TestKMS::test_all_types_of_key_id_can_be_used_for_encryption": 0.07069645499996113, + "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_delete_deleted_key": 0.04098033199966267, + "tests/aws/services/kms/test_kms.py::TestKMS::test_cant_use_disabled_or_deleted_keys": 0.06236755299983088, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_alias": 0.11289484700023422, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_custom_key_asymmetric": 0.04121693700017204, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_invalid_key": 0.028221592000136297, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_same_name_two_keys": 0.06604692199994133, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_grant_with_valid_key": 0.04682343499985109, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key": 0.12030370699994819, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_id": 0.02809100799981934, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_hmac": 0.03636735000009139, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_custom_key_material_symmetric_decrypt": 0.03125045599972509, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[lowercase_prefix]": 0.08937719200002903, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[too_long_key]": 0.08741533400007029, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_invalid_tag_key[uppercase_prefix]": 0.08990829000026679, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_tag_and_untag": 0.12007864499992138, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_key_with_too_many_tags_raises_error": 0.09014230900015718, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_list_delete_alias": 0.06631401499976164, + "tests/aws/services/kms/test_kms.py::TestKMS::test_create_multi_region_key": 0.16981902700013052, + "tests/aws/services/kms/test_kms.py::TestKMS::test_derive_shared_secret": 0.22210781399985535, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_and_list_sign_key": 0.03560502200002702, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_with_alias_arn": 0.2011828909999167, + "tests/aws/services/kms/test_kms.py::TestKMS::test_disable_and_enable_key": 0.05865028999983224, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.08849009800019303, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.04120446699994318, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_decrypt_encryption_context": 0.27733605999992506, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_1]": 0.12033700999995744, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_2048-RSAES_OAEP_SHA_256]": 0.15066015000002153, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_1]": 0.19695194599989918, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_3072-RSAES_OAEP_SHA_256]": 0.4213942670000961, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_1]": 0.8601628910000727, + "tests/aws/services/kms/test_kms.py::TestKMS::test_encrypt_validate_plaintext_size_per_key_type[RSA_4096-RSAES_OAEP_SHA_256]": 0.2604921319998539, + "tests/aws/services/kms/test_kms.py::TestKMS::test_error_messaging_for_invalid_keys": 0.2840965400000641, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_224-HMAC_SHA_224]": 0.1305206790000284, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_256-HMAC_SHA_256]": 0.13065141999982188, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_384-HMAC_SHA_384]": 0.1314701299997978, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_and_verify_mac[HMAC_512-HMAC_SHA_512]": 0.1296542740001314, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1024]": 0.08364979599991784, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[12]": 0.08898507399976552, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[1]": 0.08552660399982415, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[44]": 0.08981629699997029, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random[91]": 0.08975760499993157, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[0]": 0.08567641100012224, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[1025]": 0.08781631700003345, + "tests/aws/services/kms/test_kms.py::TestKMS::test_generate_random_invalid_number_of_bytes[None]": 0.0929640589997689, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_does_not_exist": 0.12179613300008896, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_in_different_region": 0.1413036840001496, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_key_invalid_uuid": 0.10403027899997142, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_parameters_for_import": 0.6427603530000852, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_public_key": 0.07252250399983495, + "tests/aws/services/kms/test_kms.py::TestKMS::test_get_put_list_key_policies": 0.055970401000195125, + "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key": 0.12533953199977077, + "tests/aws/services/kms/test_kms.py::TestKMS::test_hmac_create_key_invalid_operations": 0.10632260000011229, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": 0.2639340519999678, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P256]": 0.31809404599994195, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P384]": 0.29944065799986674, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P521]": 0.8852246119997744, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_SECG_P256K1]": 0.25577235199989445, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_224]": 1.9973183949998656, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_256]": 0.9663102600000002, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_384]": 1.2628536660001828, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_512]": 0.885058355999945, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_rsa_aes_wrap_sha256": 2.50053045799973, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_symmetric": 0.3104429480001727, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_224-HMAC_SHA_256]": 0.10706547499989938, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_generate_mac[HMAC_256-INVALID]": 0.10835412900019037, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_key_usage": 1.7946046380000098, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_256-some different important message]": 0.1875462579998839, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-HMAC_SHA_512-some important message]": 0.19152247000010902, + "tests/aws/services/kms/test_kms.py::TestKMS::test_invalid_verify_mac[HMAC_256-INVALID-some important message]": 0.1874118460000318, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[180]": 0.11620723000010003, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_enable_rotation_status[90]": 0.11371732100042209, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotation_status": 0.06515970700002072, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_encryption_decryption": 0.13275889800002005, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_rotations_limits": 0.24223007900013727, + "tests/aws/services/kms/test_kms.py::TestKMS::test_key_with_long_tag_value_raises_error": 0.08830003599973679, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_aliases_of_key": 0.06681186099990555, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_grants_with_invalid_key": 0.01611932199966759, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_keys": 0.029789027000106216, + "tests/aws/services/kms/test_kms.py::TestKMS::test_list_retirable_grants": 0.07639153599984638, + "tests/aws/services/kms/test_kms.py::TestKMS::test_non_multi_region_keys_should_not_have_multi_region_properties": 0.16856298700008665, + "tests/aws/services/kms/test_kms.py::TestKMS::test_plaintext_size_for_encrypt": 0.10696986699963418, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[RSA_2048-RSAES_OAEP_SHA_256]": 0.23562996499981637, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt[SYMMETRIC_DEFAULT-SYMMETRIC_DEFAULT]": 0.15143115499995474, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_incorrect_source_key": 0.12932865900006618, + "tests/aws/services/kms/test_kms.py::TestKMS::test_re_encrypt_invalid_destination_key": 0.05325205199960692, + "tests/aws/services/kms/test_kms.py::TestKMS::test_replicate_key": 0.5243291399999634, + "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_id_and_key_id": 0.062063884000053804, + "tests/aws/services/kms/test_kms.py::TestKMS::test_retire_grant_with_grant_token": 0.06165275500006828, + "tests/aws/services/kms/test_kms.py::TestKMS::test_revoke_grant": 0.06397411600005398, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_modifies_key_material": 0.12050485000008848, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_is_disabled": 0.3239977959999578, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_that_does_not_exist": 0.09057945299969106, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_key_with_imported_key_material": 0.0018799399999807065, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_raises_error_given_non_symmetric_key": 1.0738775490001444, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_disabled": 0.12218671300001915, + "tests/aws/services/kms/test_kms.py::TestKMS::test_rotate_key_on_demand_with_symmetric_key_and_automatic_rotation_enabled": 0.1396740539998973, + "tests/aws/services/kms/test_kms.py::TestKMS::test_schedule_and_cancel_key_deletion": 0.04858931399985522, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P256-ECDSA_SHA_256]": 0.3114029989997107, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_NIST_P384-ECDSA_SHA_384]": 0.3114248800000041, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.316886317999888, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_256]": 0.7672374559999753, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_384]": 0.705305694999879, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_2048-RSASSA_PSS_SHA_512]": 0.7541402860001654, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 3.1879031129999476, + "tests/aws/services/kms/test_kms.py::TestKMS::test_sign_verify[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 3.3770867549999366, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_1]": 0.15317147999985536, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_2048-RSAES_OAEP_SHA_256]": 0.10279884299984587, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_1]": 0.27404788300009386, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_3072-RSAES_OAEP_SHA_256]": 0.24024722100011786, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_1]": 0.7097413880003387, + "tests/aws/services/kms/test_kms.py::TestKMS::test_symmetric_encrypt_offline_decrypt_online[RSA_4096-RSAES_OAEP_SHA_256]": 0.6701317389997712, + "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_and_untag": 0.13047640499962654, + "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_existing_key_with_invalid_tag_key": 0.10330926499977977, + "tests/aws/services/kms/test_kms.py::TestKMS::test_tag_key_with_duplicate_tag_keys_raises_error": 0.10411767200025679, + "tests/aws/services/kms/test_kms.py::TestKMS::test_unsupported_rotate_key_on_demand_with_imported_key_material": 0.03129330300021138, + "tests/aws/services/kms/test_kms.py::TestKMS::test_untag_key_partially": 0.11998372499988363, + "tests/aws/services/kms/test_kms.py::TestKMS::test_update_alias": 0.07697205099998428, + "tests/aws/services/kms/test_kms.py::TestKMS::test_update_and_add_tags_on_tagged_key": 0.11944413100013662, + "tests/aws/services/kms/test_kms.py::TestKMS::test_update_key_description": 0.04560421800010772, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P256-ECDSA_SHA_256]": 0.048323852000066836, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_NIST_P384-ECDSA_SHA_384]": 0.04932109600008516, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[ECC_SECG_P256K1-ECDSA_SHA_256]": 0.04977700999984336, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_256]": 0.16887207800027682, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_384]": 0.1526042979999147, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_2048-RSASSA_PSS_SHA_512]": 0.18291273099998762, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_256]": 1.176945299000181, + "tests/aws/services/kms/test_kms.py::TestKMS::test_verify_salt_length[RSA_4096-RSASSA_PKCS1_V1_5_SHA_512]": 1.5059839030000148, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key": 0.1918283790002988, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair": 0.20951968200006377, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_pair_without_plaintext": 0.1422826980001446, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_encryption_context_generate_data_key_without_plaintext": 0.19024598500004686, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key": 0.043627808000110235, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair": 0.06983007699977861, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_dry_run": 0.03438162699967506, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext": 0.1216797829999905, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_pair_without_plaintext_dry_run": 0.0868502850000823, + "tests/aws/services/kms/test_kms.py::TestKMSGenerateKeys::test_generate_data_key_without_plaintext": 0.03715867199980494, + "tests/aws/services/kms/test_kms.py::TestKMSMultiAccounts::test_cross_accounts_access": 1.306616476000272, + "tests/aws/services/lambda_/event_source_mapping/test_cfn_resource.py::test_adding_tags": 17.652162621000116, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_deletion_event_source_mapping_with_dynamodb": 6.1328101430001425, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_disabled_dynamodb_event_source_mapping": 12.277767056999892, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_duplicate_event_source_mappings": 5.635448676999886, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_filter_type]": 12.808049187000051, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_multiple_filters]": 0.008692036000184089, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[content_or_filter]": 13.89142298500019, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[date_time_conversion]": 12.816146134000064, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_false_filter]": 12.819666135000034, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[exists_filter_type]": 12.770800742999882, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[insert_same_entry_twice]": 12.827357240999845, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[numeric_filter]": 12.790775433000135, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_filter[prefix_filter]": 12.808410015999698, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping": 14.87297287999968, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_on_failure_destination_config": 11.326807615000007, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_s3_on_failure_destination": 11.543918016999896, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_event_source_mapping_with_sns_on_failure_destination_config": 11.403688515000113, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[[{\"eventName\": [\"INSERT\"=123}]]": 4.5008487780000905, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_invalid_event_filter[single-string]": 4.537806974000205, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 14.856357184999979, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 14.759725443000207, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 15.814143952999757, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 14.773434540000153, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[null_item_identifier_failure]": 14.770385425000086, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 14.797299160999955, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_failures": 15.1073708670001, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 9.70151053099994, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_dict_success]": 9.778368036999836, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[empty_list_success]": 9.714800170999979, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_batch_item_failure_success]": 9.716285317000029, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_dynamodb_report_batch_item_success_scenarios[null_success]": 9.744019195999954, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py::TestDynamoDBEventSourceMapping::test_esm_with_not_existing_dynamodb_stream": 1.874305614999912, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisEventFiltering::test_kinesis_event_filtering_json_pattern": 9.25124100500011, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping": 12.10371583999995, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_create_kinesis_event_source_mapping_multiple_lambdas_single_kinesis_event_stream": 19.422451317999958, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_disable_kinesis_event_source_mapping": 29.234762796999803, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_duplicate_event_source_mappings": 3.3766773559998455, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_esm_with_not_existing_kinesis_stream": 1.4173505160001696, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_empty_provided": 11.214238875000092, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_async_invocation": 20.203295043000026, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_on_failure_destination_config": 9.238966253999934, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_s3_on_failure_destination": 9.215971667000076, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_mapping_with_sns_on_failure_destination_config": 9.217113913000048, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_event_source_trim_horizon": 26.31148255999983, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-before-ingestion]": 14.320661112000153, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded[expire-while-retrying]": 9.313817852999819, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_maximum_record_age_exceeded_discard_records": 19.335191944000144, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[empty_string_item_identifier_failure]": 12.175304787000186, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_failure]": 12.161416215000145, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[invalid_key_foo_null_value_failure]": 12.189032510999823, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[item_identifier_not_present_failure]": 12.168029321999938, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[null_item_identifier_failure]": 12.167829768000047, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failure_scenarios[unhandled_exception_in_function]": 12.163842627999884, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_failures": 12.246272727999894, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_batch_item_failure_success]": 7.1071987419998095, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_dict_success]": 7.141628609000236, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_list_success]": 7.12495919700018, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[empty_string_success]": 7.073533829000098, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_batch_item_failure_success]": 7.113772358999995, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_kinesis.py::TestKinesisSource::test_kinesis_report_batch_item_success_scenarios[null_success]": 7.126931943999807, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_duplicate_event_source_mappings": 2.587554509000256, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_event_source_mapping_default_batch_size": 3.4788856610002767, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[and]": 6.458323688999826, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[exists]": 6.429890954000257, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-bigger]": 6.436782427999788, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-range]": 6.469992751000063, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[numeric-smaller]": 6.434642092000104, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[or]": 6.415370473999701, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-filter]": 0.0022795920003773062, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[plain-string-matching]": 0.0027833759995701257, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[prefix]": 6.436911026999951, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[single]": 6.441064764000203, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_filter[valid-json-filter]": 6.450970451000103, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping": 6.3805184829998325, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[10000]": 9.567419970999708, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[1000]": 9.583223205000195, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[100]": 10.733251039000152, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size[15]": 9.56817922099981, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[10000]": 0.015272927000296477, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[1000]": 9.147363564999978, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[100]": 6.62830743800032, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batch_size_override[20]": 6.460933533000116, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_reserved_concurrency": 3.7127699469997424, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_batching_window_size_override": 26.782953231000192, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_event_source_mapping_update": 11.665751969000212, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[None]": 1.253301487999579, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter2]": 1.2298944510002912, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[invalid_filter3]": 1.2403645029999097, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::TestSQSEventSourceMapping::test_sqs_invalid_event_filter[simple string]": 1.211692168999889, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_esm_with_not_existing_sqs_queue": 1.1798239870001908, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_failing_lambda_retries_after_visibility_timeout": 16.634629034, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_fifo_message_group_parallelism": 63.51238214099931, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_message_body_and_attributes_passed_correctly": 4.543981472000041, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_redrive_policy_with_failing_lambda": 15.458278732999588, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures": 0.0024506909994670423, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_empty_json_batch_succeeds": 9.248251712000638, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_invalid_result_json_batch_fails": 15.246719177999694, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_report_batch_item_failures_on_lambda_error": 9.388416754999525, + "tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_sqs.py::test_sqs_queue_as_lambda_dead_letter_queue": 6.2698211729994, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_alias_routingconfig": 3.193544754999948, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaAliases::test_lambda_alias_moving": 3.4071876270004395, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[1]": 1.715743016000033, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_assume_role[2]": 1.747267937000288, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_function_state": 1.2448552280002332, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_different_iam_keys_environment": 3.770368851999592, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_large_response": 1.6311036900001454, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response": 1.8893670269999348, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_lambda_too_large_response_but_with_custom_limit": 1.6544954090004467, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBaseFeatures::test_large_payloads": 1.7890437410005688, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_ignore_architecture": 1.5377659089995177, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[nodejs]": 7.678574961000322, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_cache_local[python]": 1.640898895999726, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_host_prefix_api_operation": 5.891856113000358, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_init_environment": 3.6028813349998927, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_no_timeout": 3.6224961230004737, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_timed_out_environment_reuse": 0.0026850070003092696, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_lambda_invoke_with_timeout": 3.613696725000409, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_mixed_architecture": 0.002661320999777672, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_arm": 0.00271915999974226, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_introspection_x86": 1.8334205420001126, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaBehavior::test_runtime_ulimits": 1.5867818690003332, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_delete_lambda_during_sync_invoke": 0.0017706680000628694, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaCleanup::test_recreate_function": 3.3839160330003324, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_block": 13.608862747999865, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_crud": 1.2230892829993536, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_concurrency_update": 1.3741929210004855, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_moves_with_alias": 0.003458731999671727, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_lambda_provisioned_concurrency_scheduling": 8.483235324999896, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency": 2.9015510090002863, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_provisioned_concurrency_on_alias": 2.96924100599972, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency": 11.174029899999823, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_concurrency_async_queue": 3.518439486999341, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaConcurrency::test_reserved_provisioned_overlap": 13.526256739000473, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_error": 1.5684412989999146, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_handler_exit": 0.002549831000578706, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[body-n\\x87r\\x9e\\xe9\\xb5\\xd7I\\xee\\x9bmt]": 1.344320560000142, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_invoke_payload_encoding_error[message-\\x99\\xeb,j\\x07\\xa1zYh]": 1.345248205000189, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_error": 7.669394238999757, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit": 0.0017700439998407091, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_exit_segfault": 0.0015812900001037633, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_error": 1.2130275980002807, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_startup_timeout": 41.50010688800057, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaErrors::test_lambda_runtime_wrapper_not_found": 0.0018391120001979289, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[nodejs16.x]": 0.0031905609998830187, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_dry_run[python3.10]": 0.0026565030002529966, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[nodejs16.x]": 2.307107058999918, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event[python3.10]": 2.277452274999632, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_event_error": 0.0022535080006491626, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-Event]": 2.2928418559999955, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[nodejs-RequestResponse]": 2.662651867000477, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-Event]": 2.3169528710000122, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_no_return_payload[python-RequestResponse]": 2.5822386670001833, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[nodejs16.x]": 1.6403459949992794, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_type_request_response[python3.10]": 1.62783034100039, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[nodejs16.x]": 16.85580807500037, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_logs[python3.10]": 7.705768187999638, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invocation_with_qualifier": 1.828652413999862, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_invoke_exceptions": 0.10919612999941819, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_lambda_with_context": 0.0056859659994188405, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaFeatures::test_upload_lambda_from_s3": 2.1699063959999876, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_delete_function": 1.1401521119996687, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_alias": 1.1578905699998359, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_concurrency": 1.1445705780001845, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_invocation": 1.518845804999728, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_function_tags": 1.151638900999842, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function": 1.139409803000035, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_function_configuration": 1.1402083980001407, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_get_lambda_layer": 0.10438928799976566, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_list_versions_by_function": 1.1288626670002486, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaMultiAccounts::test_publish_version": 1.1916943230003199, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": 0.0026654849998521968, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_function_url_with_response_streaming": 7.923508295999909, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": 1.483645804999469, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_default": 2.1265995779999685, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_http_fixture_trim_x_headers": 2.0035402730004535, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[BUFFERED]": 1.896700520000195, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[None]": 1.9316769760002899, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_echo_invoke[RESPONSE_STREAM]": 0.012455394000426168, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_form_payload": 1.8969693799999732, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_headers_and_status": 1.6448641900001348, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invalid_invoke_mode": 1.4335360930003844, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[boolean]": 1.8447112100002414, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[dict]": 1.8231246119999014, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[float]": 1.825182735999988, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response-json]": 1.8320104559998072, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[http-response]": 1.816201285999341, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[integer]": 1.847283606000019, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[list-mixed]": 1.827126192999458, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation[string]": 1.8576374790000045, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id": 1.544024974000422, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_custom_id_aliased": 1.559599174000141, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_invocation_exception": 1.836580643999696, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_non_existing_url": 0.01660637199984194, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_url_persists_after_alias_delete": 3.9352732779998405, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_async_invoke_queue_upon_function_update": 98.73476171999982, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_function_update_during_invoke": 0.002055731999917043, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_handler_update": 2.2132218099995953, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaVersions::test_lambda_versions_with_code_changes": 5.502072178999242, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_async_invoke_with_retry": 11.65979647100039, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_format": 0.02946264499996687, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke": 3.7081228869997176, + "tests/aws/services/lambda_/test_lambda.py::TestRequestIdHandling::test_request_id_invoke_url": 3.620593624000321, + "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_code_signing_not_found_excs": 1.3093932699999868, + "tests/aws/services/lambda_/test_lambda_api.py::TestCodeSigningConfig::test_function_code_signing_config": 1.282219438000027, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings": 0.08945064999994656, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size": 1.4290860680000037, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAccountSettings::test_account_settings_total_code_size_config_update": 7.318651977999991, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_lifecycle": 2.522963506999986, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_alias_naming": 1.7029977750000285, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_deletion": 1.2075422179999578, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_non_existent_alias_update": 1.198873103999972, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaAlias::test_notfound_and_invalid_routingconfigs": 1.5018382009999414, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_exceptions": 2.7711026840000272, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventInvokeConfig::test_lambda_eventinvokeconfig_lifecycle": 1.3546153110000319, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_filter_criteria_validation": 4.366781581000055, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_self_managed": 0.0023268500000312997, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": 3.447655981999958, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": 1.9281172240000046, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": 0.15414179999999078, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle": 7.92451887499999, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_lifecycle_delete_function": 6.030559070000038, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_function_name_variations": 16.029876656, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_create_lambda_exceptions": 0.16516889199999696, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_delete_on_nonexisting_version": 1.2441090339999903, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_arns": 2.5741224000000784, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_lifecycle": 13.723951892000002, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-create_function]": 0.10472352799996543, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-delete_function]": 0.08989712800001826, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-get_function]": 0.08995888299995158, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_and_qualifier_too_long_and_invalid_region-invoke]": 0.09088726500004896, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-create_function]": 0.10346872100006976, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-delete_function]": 0.08895249499994407, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-get_function]": 0.08947306700002855, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[full_arn_with_multiple_qualifiers-invoke]": 0.09018529600001557, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-create_function]": 0.1037314559999345, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-delete_function]": 0.09162152399994739, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-get_function]": 0.08984064400004854, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_is_single_invalid-invoke]": 0.09118182499997829, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-create_function]": 0.10135846200000742, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-delete_function]": 0.08909429199997021, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-get_function]": 0.08825742899995248, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long-invoke]": 0.008639334999998027, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-create_function]": 0.10197527499991565, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-delete_function]": 0.08874274300001161, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-get_function]": 0.08795532699997466, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[function_name_too_long_and_invalid_region-invoke]": 0.08732073300001275, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-create_function]": 0.008742518000076416, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-delete_function]": 0.08922515400001885, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-get_function]": 0.08854809100000693, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[incomplete_arn-invoke]": 0.008751736000021992, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-create_function]": 0.10294038899996849, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-delete_function]": 0.08871792799993727, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-get_function]": 0.08955291500001294, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_account_id_in_partial_arn-invoke]": 0.08836843700004238, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-create_function]": 0.1060964369999624, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-delete_function]": 0.09121140900003866, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-get_function]": 0.09226937300002191, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_function_name-invoke]": 0.08991782899994405, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-create_function]": 0.1024247460000538, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-delete_function]": 0.08954656499997782, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-get_function]": 0.09034204900001441, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_characters_in_qualifier-invoke]": 0.0896285260000127, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-create_function]": 0.10209787299999107, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-delete_function]": 0.08971735199997966, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-get_function]": 0.09028332900004443, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[invalid_region_in_arn-invoke]": 0.08841475300005186, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-create_function]": 0.10278744999993705, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-delete_function]": 0.08833475299996962, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-get_function]": 0.08867103999995152, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[latest_version_with_additional_qualifier-invoke]": 0.08929001500001732, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-create_function]": 0.7358610349999708, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-delete_function]": 0.008685543000069629, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-get_function]": 0.08888461899999811, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[lowercase_latest_qualifier-invoke]": 0.08857489100000748, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-create_function]": 0.10334691699995346, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-delete_function]": 0.09040490699993597, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-get_function]": 0.09030890700000782, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_account_id_in_arn-invoke]": 0.09020292800005336, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-create_function]": 0.10326247799991961, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-delete_function]": 0.08917988599995397, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-get_function]": 0.08949125200001617, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[missing_region_in_arn-invoke]": 0.08851956099999825, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-create_function]": 0.10435349600004429, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-delete_function]": 0.08850687400007473, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-get_function]": 0.08872034299997722, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[misspelled_latest_in_arn-invoke]": 0.09030880199998137, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-create_function]": 0.10246375000002672, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-delete_function]": 0.08850886800007629, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-get_function]": 0.08993836300004432, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[non_lambda_arn-invoke]": 0.08945167600001014, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-create_function]": 0.10238135599996667, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-delete_function]": 0.08860267300002533, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-get_function]": 0.08752885100000185, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[partial_arn_with_extra_qualifier-invoke]": 0.08824251099997582, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-create_function]": 0.10237886900006288, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-delete_function]": 0.09021510000002309, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-get_function]": 0.09069339400002718, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_function_name_and_qualifier_validation[qualifier_too_long-invoke]": 0.08965353299998924, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[delete_function]": 1.2104687260000446, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function]": 1.205653017000003, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_code_signing_config]": 1.1998751890000108, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_concurrency]": 1.208248483000034, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_configuration]": 1.785210439999986, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_event_invoke_config]": 1.2210592849999102, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[get_function_url_config]": 1.1999914689999969, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_get_function_wrong_region[invoke]": 1.1937823600000002, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_invoke": 0.09041650800008938, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_security_group": 0.0016016379999541641, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_invalid_vpc_config_subnet": 0.4160658030000377, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3": 1.4596802230000776, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_s3_errors": 1.3633980639999663, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_code_location_zipfile": 1.4102938949999952, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_code_updates": 2.317564349999998, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_lambda_concurrent_config_updates": 1.2846657070000447, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_list_functions": 2.4951628209999512, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[delete_function]": 0.09127177300001676, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function]": 0.09056106799999952, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_code_signing_config]": 0.09090779400000315, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_concurrency]": 0.09033905399999753, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_configuration]": 0.08964463800000999, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_event_invoke_config]": 0.08906635900001447, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_fn[get_function_url_config]": 0.091209847999977, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function]": 1.209185553999987, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_configuration]": 1.1973259740000515, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_on_nonexisting_version[get_function_event_invoke_config]": 1.2016987979999953, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[delete_function]": 0.09777560899999571, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function]": 0.09991348599999128, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_ops_with_arn_qualifier_mismatch[get_function_configuration]": 0.0988736560000234, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_redundant_updates": 1.3186642600000198, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_update_lambda_exceptions": 1.2290765089999809, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaFunction::test_vpc_config": 2.1028240129999745, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_and_image_config_crud": 1.9255757650000191, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_crud": 7.928947185999959, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_image_versions": 2.9572276279999983, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaImages::test_lambda_zip_file_to_image": 1.9984605299999885, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes0]": 0.1281538009999963, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_compatibilities[runtimes1]": 0.12930027099997687, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_deterministic_version": 0.06166032700002688, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_exceptions": 0.28988639799996463, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_exceptions": 17.497525427000028, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_function_quota_exception": 16.384545916000093, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_lifecycle": 1.4621591910000689, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_exceptions": 0.23655731599990304, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_policy_lifecycle": 0.1790437509999947, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaLayer::test_layer_s3_content": 0.20716238099998918, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_aws": 1.2372057880000966, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_add_lambda_permission_fields": 1.2983481359999587, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_create_multiple_lambda_permissions": 1.2274902139999995, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_lambda_permission_fn_versioning": 1.4281530710000538, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_permission_exceptions": 1.3346248949999335, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaPermissions::test_remove_multi_permissions": 1.2691374259999861, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_lambda_provisioned_lifecycle": 2.457054287999995, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_exceptions": 1.3780295419999788, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaProvisionedConcurrency::test_provisioned_concurrency_limits": 1.2625660749999383, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_allow": 1.2075026850000086, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_default_terminate": 1.188635168000019, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRecursion::test_put_function_recursion_config_invalid_value": 1.203659330999983, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency": 1.2420152430000257, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_exceptions": 1.2317465099999936, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaReservedConcurrency::test_function_concurrency_limits": 1.2232136320000109, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_basic": 13.65734877500006, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_permissions": 1.272436824999943, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaRevisions::test_function_revisions_version_and_alias": 1.3525960060000557, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_lambda_envvars_near_limit_succeeds": 1.2894431840000493, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_fails_multiple_keys": 16.210613676000037, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_environment_variables_fails": 16.21563124299996, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_large_lambda": 12.624516481000057, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_request_create_lambda": 2.074481588000026, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_unzipped_lambda": 4.721472542999948, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSizeLimits::test_oversized_zipped_create_lambda": 1.5891340940000305, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_exceptions": 0.10331627600010052, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[dotnet8]": 5.338170203000118, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java11]": 3.30111066500001, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java17]": 3.3172313340000983, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[java21]": 3.3044813179999437, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.12]": 1.2593709010000111, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_lifecycle[python3.13]": 7.347952735999911, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[dotnet8]": 1.2356780389999358, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java11]": 1.2210424830000193, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java17]": 1.2238856779999878, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[java21]": 1.250433674000078, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.12]": 1.2424129989999528, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaSnapStart::test_snapstart_update_function_configuration[python3.13]": 1.2512572299999647, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_esm_create": 1.5910166619999586, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_create_tag_on_fn_create": 1.2155130589999885, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[event_source_mapping]": 0.1206065620000345, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_exceptions[lambda_function]": 0.12180495500007282, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[event_source_mapping]": 1.436026906000052, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_lifecycle[lambda_function]": 1.3018318009999916, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTag::test_tag_nonexisting_resource": 1.2554232810000485, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_exceptions": 1.3138382299999876, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_lifecycle": 1.3715838330000452, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_limits": 1.3737795089999736, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaTags::test_tag_versions": 1.266399535000005, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag": 1.129387706999978, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_alias": 3.4295335539999883, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_create_url_config_custom_id_tag_invalid_id": 1.1306030169999985, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_deletion_without_qualifier": 2.0604363159999934, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_exceptions": 7.59216974900005, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_lifecycle": 1.313913461000027, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaUrl::test_url_config_list_paging": 1.3763133400000243, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_version_on_create": 1.2967548460000557, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_update": 2.0111053749999996, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_publish_with_wrong_sha256": 1.254509373000019, + "tests/aws/services/lambda_/test_lambda_api.py::TestLambdaVersions::test_version_lifecycle": 1.479168522000009, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_advanced_logging_configuration_format_switch": 3.502206330000007, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_advanced_logging_configuration": 2.5080269699999747, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config0]": 1.417344557000007, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config1]": 1.3985067830000162, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config2]": 1.4096385590000295, + "tests/aws/services/lambda_/test_lambda_api.py::TestLoggingConfig::test_function_partial_advanced_logging_configuration_update[partial_config3]": 1.4647175190000041, + "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_cross_region_arn_function_access": 1.3507121710000263, + "tests/aws/services/lambda_/test_lambda_api.py::TestPartialARNMatching::test_update_function_configuration_full_arn": 16.383927649000015, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_disabled": 33.18328262899999, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[dotnetcore3.1]": 0.1041603410000107, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[go1.x]": 0.12246923999998671, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[java8]": 0.13213349800003016, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs12.x]": 0.10764928800000462, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[nodejs14.x]": 0.10675453000001767, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[provided]": 0.11383537899999396, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[python3.7]": 0.10715100800001665, + "tests/aws/services/lambda_/test_lambda_api.py::TestRuntimeValidation::test_create_deprecated_function_runtime_with_validation_enabled[ruby2.7]": 0.1173910330000183, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet6]": 1.9270445210000844, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[dotnet8]": 1.858470600000146, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java11]": 4.915878174999989, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java17]": 4.194316222999987, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java21]": 4.124462625999854, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[java8.al2]": 6.188002084999994, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs16.x]": 1.824827687999914, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs18.x]": 1.7361054939999576, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs20.x]": 1.7065615010000101, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[nodejs22.x]": 1.7475697610000225, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.10]": 1.737339370000086, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.11]": 1.7094234549999783, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.12]": 1.769782085000088, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.13]": 1.732858647999933, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.8]": 1.7275180220000266, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[python3.9]": 1.729499687999919, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.2]": 3.3007996339998726, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.3]": 2.3440590680000923, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaCallingLocalstack::test_manual_endpoint_injection[ruby3.4]": 2.0507170690000294, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet6]": 4.582589883999958, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[dotnet8]": 2.5041360589999613, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java11]": 2.4715282980000666, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java17]": 2.380424703000017, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java21]": 2.534516660999998, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[java8.al2]": 5.534042623999994, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs16.x]": 10.56676107200019, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs18.x]": 2.480434839000054, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs20.x]": 2.428295688999924, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[nodejs22.x]": 7.431729958999881, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2023]": 4.19178301099987, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[provided.al2]": 5.1911100929999066, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.10]": 7.620874925999942, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.11]": 2.594465540999977, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.12]": 2.5603976790000615, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.13]": 2.5923201780001364, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.8]": 2.7321153219999132, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[python3.9]": 7.629236817999981, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.2]": 8.61184701000002, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.3]": 9.609181893000027, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_echo_invoke[ruby3.4]": 9.555202822999945, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet6]": 3.6062072649999664, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[dotnet8]": 3.5811048159999928, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java11]": 3.699052055999914, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java17]": 3.6469399630000225, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java21]": 3.696212332000073, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[java8.al2]": 3.881452384000113, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs16.x]": 3.5060097399999677, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs18.x]": 3.5590223469999955, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs20.x]": 3.591475963999983, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[nodejs22.x]": 4.347533272000078, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2023]": 3.522548358999984, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[provided.al2]": 3.509009869000124, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.10]": 4.455096062000052, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.11]": 3.468133754000064, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.12]": 3.4457150670000374, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.13]": 3.522805010999946, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.8]": 3.4819804350000823, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[python3.9]": 3.4622176799999806, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.2]": 4.553243716000111, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.3]": 3.5280541700000185, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_introspection_invoke[ruby3.4]": 3.6001964040000303, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet6]": 1.8352923589999364, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[dotnet8]": 1.7924947669999938, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java11]": 1.9616003710000314, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java17]": 1.8481020009999156, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java21]": 1.8330156519999719, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[java8.al2]": 2.0960521529999596, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs16.x]": 1.72230451300004, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs18.x]": 1.735580837999919, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs20.x]": 1.6852470999999696, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[nodejs22.x]": 1.699570670000071, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.10]": 1.6783096090001663, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.11]": 1.6887267430000747, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.12]": 1.6854885469999772, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.13]": 1.6955268750000414, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.8]": 1.745758488999968, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[python3.9]": 1.6530683219999673, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.2]": 1.7598577229999819, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.3]": 1.7527496249999786, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_runtime_wrapper_invoke[ruby3.4]": 1.7542728630000965, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet6]": 1.8123684789999288, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[dotnet8]": 1.8236766339999804, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java11]": 1.9632215350000024, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java17]": 1.844542080999986, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java21]": 1.8575381590000006, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[java8.al2]": 2.1488555489999044, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs16.x]": 1.7233220780000238, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs18.x]": 1.7471742729999278, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs20.x]": 1.7162688450000587, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[nodejs22.x]": 1.7025449900000922, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2023]": 1.7450152139999773, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[provided.al2]": 1.745733697999981, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.10]": 1.6962713970000323, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.11]": 2.6504291979999834, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.12]": 1.6737663290000455, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.13]": 1.6980279419999533, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.8]": 1.7045515979999664, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[python3.9]": 1.7010210660000666, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.2]": 1.7620179779999035, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.3]": 1.7823132340000711, + "tests/aws/services/lambda_/test_lambda_common.py::TestLambdaRuntimesCommon::test_uncaught_exception_invoke[ruby3.4]": 1.7642776520000325, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDLQ::test_dead_letter_queue": 20.27165821999995, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationEventbridge::test_invoke_lambda_eventbridge": 15.744145147999916, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload0]": 1.9110918940001511, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_assess_lambda_destination_invocation[payload1]": 1.877962231999959, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_lambda_destination_default_retries": 21.39445466899997, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_maxeventage": 62.93250078599999, + "tests/aws/services/lambda_/test_lambda_destinations.py::TestLambdaDestinationSqs::test_retries": 22.503328396000143, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_additional_docker_flags": 1.5774405470000374, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestDockerFlags::test_lambda_docker_networks": 6.193591598000012, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[nodejs20.x]": 3.3938339590000624, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading[python3.12]": 4.352101580000067, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_environment_placeholder": 0.4865756199999396, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_error_path_not_absolute": 0.024977252999974553, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestHotReloading::test_hot_reloading_publish_version": 1.0999168800000234, + "tests/aws/services/lambda_/test_lambda_developer_tools.py::TestLambdaDNS::test_lambda_localhost_localstack_cloud_connectivity": 1.5487196960004894, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[Active]": 2.602906659999917, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_traceid_outside_handler[PassThrough]": 2.566219748999856, + "tests/aws/services/lambda_/test_lambda_integration_xray.py::test_xray_trace_propagation": 1.5307993940002689, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestCloudwatchLogs::test_multi_line_prints": 3.599928525000223, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2023]": 1.849733280999999, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_manual_endpoint_injection[provided.al2]": 1.8614802349998172, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2023]": 1.9535361129999274, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestGoProvidedRuntimes::test_uncaught_exception_invoke[provided.al2]": 1.9544584839998151, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom-INTERFACE]": 3.0488381699999536, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequest-INTERFACE]": 3.03401021100035, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_custom_handler_method_specification[cloud.localstack.sample.LambdaHandlerWithInterfaceAndCustom::handleRequestCustom-CUSTOM]": 3.0560439820001193, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_lambda_subscribe_sns_topic": 8.879120788000137, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_java_runtime_with_lib": 5.613107006999826, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java11]": 2.7065934859999743, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java17]": 3.6178928990002532, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java21]": 3.13302278299966, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_serializable_input_object[java8.al2]": 2.8070614939999814, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java11]": 1.7336452950000876, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java17]": 1.6884463910000704, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java21]": 1.8731152950003889, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestJavaRuntimes::test_stream_handler[java8.al2]": 1.7161975260000872, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs16.x]": 4.723419235999927, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs18.x]": 4.695720410999911, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs20.x]": 4.682175378000011, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestNodeJSRuntimes::test_invoke_nodejs_es6_lambda[nodejs22.x]": 4.679047719999971, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.10]": 1.691929410000057, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.11]": 1.6605397400001038, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.12]": 1.6586501089998364, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.13]": 1.649857382999926, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.8]": 1.665384136999819, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_handler_in_submodule[python3.9]": 1.668502798000418, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.10]": 1.5247519530000773, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.11]": 1.542376143999718, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.12]": 1.5416351680000844, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.13]": 1.5442149839998365, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.8]": 1.5614838870001222, + "tests/aws/services/lambda_/test_lambda_runtimes.py::TestPythonRuntimes::test_python_runtime_correct_versions[python3.9]": 1.5371315309998863, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_group": 0.2002112140000918, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_create_and_delete_log_stream": 0.48044033700011823, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_delivery_logs_for_sns": 1.1006499050001821, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_filter_log_events_response_header": 0.054514777999884245, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_list_tags_log_group": 0.22095416100023613, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_metric_filters": 0.0018290930001967354, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_events_multi_bytes_msg": 0.05584836300022289, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_firehose": 0.49030563599990273, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_kinesis": 2.373570528999835, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_put_subscription_filter_lambda": 1.9525170290000915, + "tests/aws/services/logs/test_logs.py::TestCloudWatchLogs::test_resource_does_not_exist": 0.03986869900018064, + "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend": 0.15696697400017, + "tests/aws/services/opensearch/test_opensearch.py::TestCustomBackendManager::test_custom_backend_with_custom_endpoint": 0.17330443400010154, + "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint": 9.930463530000225, + "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_custom_endpoint_disabled": 10.435612416999902, + "tests/aws/services/opensearch/test_opensearch.py::TestEdgeProxiedOpensearchCluster::test_route_through_edge": 9.826987164999991, + "tests/aws/services/opensearch/test_opensearch.py::TestMultiClusterManager::test_multi_cluster": 19.085507420999875, + "tests/aws/services/opensearch/test_opensearch.py::TestMultiplexingClusterManager::test_multiplexing_cluster": 10.791164002999949, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_cloudformation_deployment": 16.273765895000224, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_custom_endpoint": 0.02316622299986193, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_domain_with_invalid_name": 0.026746782000145686, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_existing_domain_causes_exception": 10.427015082000025, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_create_indices": 11.766888777999839, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_describe_domains": 9.991651150000052, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_lifecycle": 13.535672133999924, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_domain_version": 9.93916028999979, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_path": 10.443504720999954, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_endpoint_strategy_port": 10.404642219000152, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_exception_header_field": 0.01190052000038122, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_version_for_domain": 8.910627170999987, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_compatible_versions": 0.023344917000258647, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_get_document": 11.657627411000021, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_gzip_responses": 10.026603788999864, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_list_versions": 0.10082345100022394, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_search": 10.957708425000192, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_security_plugin": 16.031851430000188, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_sql_plugin": 14.816671895999889, + "tests/aws/services/opensearch/test_opensearch.py::TestOpensearchProvider::test_update_domain_config": 10.526063022999779, + "tests/aws/services/opensearch/test_opensearch.py::TestSingletonClusterManager::test_endpoint_strategy_port_singleton_cluster": 10.372424709999905, + "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_cluster_security_groups": 0.037910890000148356, + "tests/aws/services/redshift/test_redshift.py::TestRedshift::test_create_clusters": 0.18042820700020457, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_cloudformation_query": 0.0016359520002424688, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_create_group": 0.447313658999974, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_different_region": 0.0016440869997040863, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_groups_tag_query": 0.0018210670000371465, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_resource_type_filters": 0.0016329769998719712, + "tests/aws/services/resource_groups/test_resource_groups.py::TestResourceGroups::test_search_resources": 0.0016422330002114904, + "tests/aws/services/resourcegroupstaggingapi/test_rgsa.py::TestRGSAIntegrations::test_get_resources": 0.5164282109999476, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_associate_vpc_with_hosted_zone": 0.5318369149997579, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone": 0.6485915070002193, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_hosted_zone_in_non_existent_vpc": 0.18994025999995756, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_create_private_hosted_zone": 0.7252092840001296, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_crud_health_check": 0.1954805650000253, + "tests/aws/services/route53/test_route53.py::TestRoute53::test_reusable_delegation_sets": 0.1690855950000696, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_associate_and_disassociate_resolver_rule": 0.5020595680000497, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[INBOUND-5]": 0.3599545560000479, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_endpoint[OUTBOUND-10]": 0.2985209799999211, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_query_log_config": 0.321487912000066, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule": 0.3977708849999999, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_create_resolver_rule_with_invalid_direction": 0.31018288899986146, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_endpoint": 0.08876850000001468, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_query_log_config": 0.15734466499998234, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_non_existent_resolver_rule": 0.09040155899970159, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_delete_resolver_endpoint": 0.30541354100000717, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_disassociate_non_existent_association": 0.08904677100031222, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_domain_lists": 0.1897993029999725, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules": 0.322146524000118, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_empty_rule_group": 0.10722374300007687, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_list_firewall_rules_for_missing_rule_group": 0.15977963599993927, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multipe_create_resolver_rule": 0.4328708270002153, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_multiple_create_resolver_endpoint_with_same_req_id": 0.3115208839999468, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_route53resolver_bad_create_endpoint_security_groups": 0.20285749399999986, + "tests/aws/services/route53resolver/test_route53resolver.py::TestRoute53Resolver::test_update_resolver_endpoint": 0.3193802109999524, + "tests/aws/services/s3/test_s3.py::TestS3::test_access_bucket_different_region": 0.0018776049998905364, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_availability": 0.03571350599986545, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_does_not_exist": 0.4464292509999268, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_exists": 0.24497567799994613, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_name_with_dots": 0.5558851599998889, + "tests/aws/services/s3/test_s3.py::TestS3::test_bucket_operation_between_regions": 0.47127712999986215, + "tests/aws/services/s3/test_s3.py::TestS3::test_complete_multipart_parts_order": 0.4682118920002267, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_in_place_with_bucket_encryption": 0.1614649499999814, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_kms": 0.6604824479998115, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character": 0.6690752809997775, + "tests/aws/services/s3/test_s3.py::TestS3::test_copy_object_special_character_plus_for_space": 0.09748793099993236, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_head_bucket": 1.8570102389999192, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_via_host_name": 0.05177868500027216, + "tests/aws/services/s3/test_s3.py::TestS3::test_create_bucket_with_existing_name": 0.4346629149997625, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_no_such_bucket": 0.020841252999844073, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy": 0.09719054400011373, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_policy_expected_bucket_owner": 0.1069144979999237, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_bucket_with_content": 1.9151988209998763, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_keys_in_versioned_bucket": 0.5503491489998851, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys": 0.08733923499994489, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_in_non_existing_bucket": 0.0238389419998839, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_non_existing_keys_quiet": 0.08692580200022348, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_object_tagging": 0.1135250959998757, + "tests/aws/services/s3/test_s3.py::TestS3::test_delete_objects_encoding": 0.11422763699988536, + "tests/aws/services/s3/test_s3.py::TestS3::test_different_location_constraint": 0.6079076130001795, + "tests/aws/services/s3/test_s3.py::TestS3::test_download_fileobj_multiple_range_requests": 1.1358156150001832, + "tests/aws/services/s3/test_s3.py::TestS3::test_empty_bucket_fixture": 0.1455536050000319, + "tests/aws/services/s3/test_s3.py::TestS3::test_etag_on_get_object_call": 0.46188558499989085, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_notification_configuration_no_such_bucket": 0.021558373000061692, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy": 0.13894731500022317, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000000000020]": 0.07696350399965013, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[0000]": 0.08027683499994964, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[aa000000000$]": 0.06825350100007199, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_policy_invalid_account_id[abcd]": 0.06886553599974832, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_bucket_versioning_order": 0.5181884629998876, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_after_deleted_in_versioned_bucket": 0.11476854400007142, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes": 0.32626421700024366, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_versioned": 0.49812640999994073, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_attributes_with_space": 0.09906697099995654, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[False]": 0.09467208999990362, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_content_length_with_virtual_host[True]": 0.09425478099979046, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_no_such_bucket": 0.0217414779997398, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part": 0.23631715800001984, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[COMPOSITE]": 0.1238342389999616, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_part_checksum[FULL_OBJECT]": 0.1322484259999328, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_object_with_anon_credentials": 0.5238694289998875, + "tests/aws/services/s3/test_s3.py::TestS3::test_get_range_object_headers": 0.09067355199999838, + "tests/aws/services/s3/test_s3.py::TestS3::test_head_object_fields": 0.09683051299975887, + "tests/aws/services/s3/test_s3.py::TestS3::test_invalid_range_error": 0.10532578499987721, + "tests/aws/services/s3/test_s3.py::TestS3::test_metadata_header_character_decoding": 0.4478406109999469, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_and_list_parts": 0.18235594399993715, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_too_small": 0.11242600399987168, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_complete_multipart_wrong_part": 0.10023214800003188, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_copy_object_etag": 0.13579985900037173, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_no_such_upload": 0.09013958999980787, + "tests/aws/services/s3/test_s3.py::TestS3::test_multipart_overwrite_key": 0.12115645000017139, + "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[False]": 0.18814231800024572, + "tests/aws/services/s3/test_s3.py::TestS3::test_object_with_slashes_in_key[True]": 0.19174266799973338, + "tests/aws/services/s3/test_s3.py::TestS3::test_precondition_failed_error": 0.09729782699992029, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_content_language_disposition": 0.9172779999998966, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_hash_prefix": 0.44914648499980103, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_and_get_object_with_utf8_key": 0.4470433439998942, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_inventory_config_order": 0.1589520040001844, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy": 0.09390227400012918, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_expected_bucket_owner": 0.18515147500011153, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000000000020]": 0.06752917499989053, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[0000]": 0.06623971100020754, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[aa000000000$]": 0.06656615799988685, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_bucket_policy_invalid_account_id[abcd]": 0.0665343099999518, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_single_character_trailing_slash": 0.15833691599982558, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[a/%F0%9F%98%80/]": 0.4597133249999388, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[file%2Fname]": 0.46569729699990603, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key//]": 0.465671346000363, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test key/]": 0.46226520899995194, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123/]": 0.466210263999983, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%123]": 0.4671712180002032, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test%percent]": 0.4642792560000544, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_get_object_special_character[test@key/]": 0.459538705000341, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_acl_on_delete_marker": 0.5342930179999712, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_checksum": 0.10099173099979453, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_content_encoding": 0.10520743799997945, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines": 0.08318856100004268, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig": 0.08938083400016694, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_no_sig_empty_body": 0.08429747599984694, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_chunked_newlines_with_trailing_checksum": 0.10353825400011374, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[DEEP_ARCHIVE-False]": 0.09623921399975188, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER-False]": 0.09692993199996636, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[GLACIER_IR-True]": 0.09798036200027127, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[INTELLIGENT_TIERING-True]": 0.09734152000009999, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[ONEZONE_IA-True]": 0.09928892300013104, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[REDUCED_REDUNDANCY-True]": 0.09849355000005744, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD-True]": 0.09861871500015695, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class[STANDARD_IA-True]": 0.10087519500007147, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_storage_class_outposts": 0.07908078999980717, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_tagging_empty_list": 0.122000584000034, + "tests/aws/services/s3/test_s3.py::TestS3::test_put_object_with_md5_and_chunk_signature": 0.08905562499990083, + "tests/aws/services/s3/test_s3.py::TestS3::test_putobject_with_multiple_keys": 0.4513532959999793, + "tests/aws/services/s3/test_s3.py::TestS3::test_range_header_body_length": 0.10796295299996927, + "tests/aws/services/s3/test_s3.py::TestS3::test_range_key_not_exists": 0.08118886400006886, + "tests/aws/services/s3/test_s3.py::TestS3::test_region_header_exists_outside_us_east_1": 0.5662673859999359, + "tests/aws/services/s3/test_s3.py::TestS3::test_response_structure": 0.1625836610000988, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_analytics_configurations": 0.21440551900013816, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects": 0.4926959709998755, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_objects_using_requests_with_acl": 0.0019781400001193106, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_batch_delete_public_objects_using_requests": 0.4746889469997768, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl": 0.15351041899998563, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_bucket_acl_exceptions": 0.19690704300001016, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_content_type_and_metadata": 0.5047989480001434, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_directive_copy": 0.4672804519998408, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_metadata_replace": 0.4717780380001386, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place": 0.5270209399998294, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_metadata_directive": 0.5729543209997701, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_storage_class": 0.4774545569998736, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_suspended_only": 0.568069893000029, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_versioned": 0.617975751999893, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_website_redirect_location": 0.4713053100001616, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_in_place_with_encryption": 1.979300147999993, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_preconditions": 3.5412783879999097, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_storage_class": 0.5085805930000333, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32C]": 0.48247727100033444, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC32]": 0.4844574480000574, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[CRC64NVME]": 0.4917102800000066, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA1]": 0.4885898819998147, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_checksum[SHA256]": 0.4927651240002433, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32C]": 0.509133496000004, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC32]": 0.49766312200017637, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[CRC64NVME]": 0.49901073699993503, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA1]": 0.4975314840003193, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_with_default_checksum[SHA256]": 0.5034974069999407, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_object_wrong_format": 0.455039442000043, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[COPY]": 0.48684108199995535, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[None]": 0.4887389330001497, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive[REPLACE]": 0.4898996640001769, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[COPY]": 0.5812597799999821, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[None]": 0.5902684240002145, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_copy_tagging_directive_versioned[REPLACE]": 0.5846537350000744, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_object_with_version_id": 0.5124352830000589, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_delete_objects_trailing_slash": 0.0749271059999046, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_download_object_with_lambda": 4.2654498750000585, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_header_overrides": 0.09751088700022592, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_headers": 0.15531174200009445, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[get_object]": 3.551042219999772, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_get_object_preconditions[head_object]": 3.5419680520001293, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_hostname_with_subdomain": 0.022035722000055102, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_intelligent_tier_config": 0.1618214740001349, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_invalid_content_md5": 12.990325433999942, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_inventory_report_crud": 0.1684753230001661, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_lambda_integration": 12.272301479000134, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_acls": 0.20091835199991692, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_multipart_upload_sse": 0.19965654099996755, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl": 0.17212657400000353, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_acl_exceptions": 0.23339431900012642, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_object_expires": 0.5089009030000398, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_inventory_report_exceptions": 0.1591994349998913, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_more_than_1000_items": 13.029897945999892, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_put_object_versioned": 0.6441922999999861, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_raw_request_routing": 0.14534097900013876, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer": 0.08361028399986026, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_request_payer_exceptions": 0.08252231699998447, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_bucket_key_default": 0.22879290599985325, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_default_kms_key": 0.0018744969997896987, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key": 0.26912556599995696, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_sse_validate_kms_key_state": 0.28654727200000707, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_timestamp_precision": 0.11102123699993172, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_upload_download_gzip": 0.09275048900008187, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_bucket_name": 0.37837888000012754, + "tests/aws/services/s3/test_s3.py::TestS3::test_s3_uppercase_key_names": 0.10010871700023927, + "tests/aws/services/s3/test_s3.py::TestS3::test_set_external_hostname": 0.13888351800005694, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_big_file": 0.6462365560000762, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_multipart": 0.4800202310000259, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_file_with_xml_preamble": 0.44651338199992097, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_cancelled_valid_etag": 0.11812474600037604, + "tests/aws/services/s3/test_s3.py::TestS3::test_upload_part_chunked_newlines_valid_etag": 0.10343484400004854, + "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[False]": 0.1430630510001265, + "tests/aws/services/s3/test_s3.py::TestS3::test_url_encoded_key[True]": 0.1456333139999515, + "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxy_does_not_decode_gzip": 0.09113000599995758, + "tests/aws/services/s3/test_s3.py::TestS3::test_virtual_host_proxying_headers": 0.09188545900019562, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_date": 0.07714867599997888, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry": 0.12027520800006641, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_configuration_object_expiry_versioned": 0.16671964900001512, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_multiple_rules": 0.12490070799981368, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_object_size_rules": 0.12239674500006004, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_bucket_lifecycle_tag_rules": 0.18708788499998263, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_bucket_lifecycle_configuration": 0.12377994300004502, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_delete_lifecycle_configuration_on_bucket_deletion": 0.13246042000014313, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_lifecycle_expired_object_delete_marker": 0.10944534700001896, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_object_expiry_after_bucket_lifecycle_configuration": 0.12806691599985243, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_put_bucket_lifecycle_conf_exc": 0.15323603800015917, + "tests/aws/services/s3/test_s3.py::TestS3BucketLifecycle::test_s3_transition_default_minimum_object_size": 0.12200039799995466, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging": 0.14583086099992215, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_accept_wrong_grants": 0.13124770500030536, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_cross_locations": 0.16740691200016045, + "tests/aws/services/s3/test_s3.py::TestS3BucketLogging::test_put_bucket_logging_wrong_target": 0.11873782699967705, + "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config": 0.6556647859999885, + "tests/aws/services/s3/test_s3.py::TestS3BucketReplication::test_replication_config_without_filter": 0.6280579249998937, + "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_s3_get_deep_archive_object_restore": 0.5539725720000206, + "tests/aws/services/s3/test_s3.py::TestS3DeepArchive::test_storage_class_deep_archive": 0.1658143170002404, + "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_access": 0.1298853510002118, + "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_cross_account_copy_object": 0.09410698599981515, + "tests/aws/services/s3/test_s3.py::TestS3MultiAccounts::test_shared_bucket_namespace": 0.07003547299996171, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32C]": 0.44309591099977297, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[CRC32]": 0.5049167450001733, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA1]": 0.5124194139998508, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_composite[SHA256]": 0.5194332419998773, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_default": 0.2100476109999363, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32C]": 0.5602199449999716, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC32]": 0.6091056150000895, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object[CRC64NVME]": 0.5925395200001731, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_complete_multipart_parts_checksum_full_object_default": 0.12876323700015746, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32C]": 0.07394655799998873, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC32]": 0.07614485499993862, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-CRC64NVME]": 0.0780217970002468, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA1]": 0.07456320500023139, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[COMPOSITE-SHA256]": 0.07457059299986213, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32C]": 1.301767546000292, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC32]": 0.07579372400027751, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-CRC64NVME]": 0.07020106600020881, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA1]": 0.07264882799995576, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_compatibility[FULL_OBJECT-SHA256]": 0.06892884200010485, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32C]": 0.07377512000016395, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC32]": 0.07450871300011386, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[CRC64NVME]": 0.07104741299986017, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA1]": 0.07935812400000941, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_checksum_type_default_for_checksum[SHA256]": 0.07059496300007595, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_composite": 5.215570540000044, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_parts_checksum_exceptions_full_object": 32.619421915999965, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_size_validation": 0.12076030299976992, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32C]": 6.803700847999835, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC32]": 9.77830942599985, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[CRC64NVME]": 9.638109579000002, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA1]": 4.889866731999746, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_checksum_exception[SHA256]": 4.285666065999976, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[COMPOSITE]": 0.15798103299994182, + "tests/aws/services/s3/test_s3.py::TestS3MultipartUploadChecksum::test_multipart_upload_part_copy_checksum[FULL_OBJECT]": 0.15448146399990037, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_delete_locked_object": 0.12650954300011108, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_get_object_legal_hold": 0.1350243640001736, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_legal_hold_exc": 0.16905056600012358, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_put_object_with_legal_hold": 0.10678538399997706, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_copy_object_legal_hold": 0.4957734380000147, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockLegalHold::test_s3_legal_hold_lock_versioned": 0.5648257099999228, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_bucket_config_default_retention": 0.14160305900009007, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_delete_markers": 0.12669432399979996, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_object_lock_extend_duration": 0.13155320099986056, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_copy_object_retention_lock": 0.4842893419997836, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_lock_mode_validation": 0.10499924400005511, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention": 6.165481243000158, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_compliance_mode": 6.150488074000123, + "tests/aws/services/s3/test_s3.py::TestS3ObjectLockRetention::test_s3_object_retention_exc": 0.250472703000014, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_default_checksum": 0.09584331800010659, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3]": 0.09941424899989215, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_casing[s3v4]": 0.0989316210002471, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": 0.329440133999924, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": 0.28391438900007415, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": 0.2328519130001041, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": 0.3335467529998368, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_files": 0.09038128800011691, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_metadata": 0.09753888100021868, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_storage_class": 0.32254591400010213, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[invalid]": 0.1657781330002308, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[list]": 0.1670737120002741, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[notxml]": 0.1568089779998445, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_tags[single]": 0.1660999770001581, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_wrong_content_type": 0.1430572619999566, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_expires": 3.149892667999893, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3]": 0.15844756800015602, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_malformed_policy[s3v4]": 0.15832072900025196, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3]": 0.16728645999978653, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_fields[s3v4]": 0.16684653900028934, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3]": 0.15430050500026482, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_request_missing_signature[s3v4]": 0.15719573299998046, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_presigned_post_with_different_user_credentials": 0.19595368299997062, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_redirect": 0.09457142800010843, + "tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_s3_presigned_post_success_action_status_201_response": 0.07759199200017974, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_delete_has_empty_content_length_header": 0.09295795699995324, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_object_ignores_request_body": 0.08893747299998722, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_get_request_expires_ignored_if_validation_disabled": 3.1074384799997006, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_head_has_correct_content_length_header": 0.08413714799985428, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_forward_slash_bucket": 0.10350553600028434, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_match": 0.10420808200024112, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_pre_signed_url_if_none_match": 0.09963392299982843, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_check_signature_validation_for_port_permutation": 0.10622718599984182, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presign_with_additional_query_params": 0.11389463900013652, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_double_encoded_credentials": 0.17252924599984, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-False]": 0.22670794199984812, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3-True]": 0.22863723199998276, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-False]": 0.23021652799980075, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication[s3v4-True]": 0.23797947200000635, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-False]": 2.1719563140002265, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3-True]": 2.174932025000089, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-False]": 2.175523658999964, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_expired[s3v4-True]": 2.174098816000196, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-False]": 0.12091197700033263, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3-True]": 0.12042572299992571, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-False]": 0.11979575700001988, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_signature_authentication_multi_part[s3v4-True]": 0.1239307230000577, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_signed_headers_in_qs": 1.9450447550000263, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_v4_x_amz_in_qs": 8.494359798000232, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_different_user_credentials": 0.1896978899997066, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_presigned_url_with_session_token": 0.11873538499980896, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object": 0.4465837019999981, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-False]": 0.09562712799993278, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3-True]": 0.16541165799981172, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-False]": 0.09175168000001577, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_object_with_md5_and_chunk_signature_bad_headers[s3v4-True]": 0.16624904999980572, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[False]": 1.6331191469998885, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3[True]": 0.5412959899997531, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[False]": 0.5566399879999153, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_put_url_metadata_with_sig_s3v4[True]": 0.5544200399999681, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_copy_md5": 0.1121771489999901, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_case_sensitive_headers": 0.08565067900008216, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_content_type_same_as_upload_and_range": 0.09806268499983162, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_default_content_type": 0.08526323299997784, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3]": 0.11322723600005702, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_get_response_header_overrides[s3v4]": 0.11413953800001764, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_ignored_special_headers": 0.1309819460000199, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3]": 0.09829927100008717, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presign_url_encoding[s3v4]": 0.09959002500022507, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3]": 3.1956117779998294, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_presigned_url_expired[s3v4]": 3.1972774020000543, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3]": 0.1723548329998721, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_missing_sig_param[s3v4]": 0.1742188289999831, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_same_header_and_qs_parameter": 0.18429297099987707, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3]": 1.3147266640000908, + "tests/aws/services/s3/test_s3.py::TestS3PresignedUrl::test_s3_put_presigned_url_with_different_headers[s3v4]": 0.21794022300036886, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32C]": 7.618470749999915, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC32]": 10.900541532000034, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[CRC64NVME]": 9.663474232000226, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA1]": 12.356801960999974, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_put_object_checksum[SHA256]": 11.251907833000132, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_algorithm": 0.11695807700016303, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_no_automatic_sdk_calculation": 0.2571461500001533, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_checksum_with_content_encoding": 0.11763593900013802, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32C]": 0.1317670220000764, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC32]": 0.13178070799995112, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[CRC64NVME]": 0.12710776699987036, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[None]": 0.1276280439999482, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA1]": 0.13172476600016125, + "tests/aws/services/s3/test_s3.py::TestS3PutObjectChecksum::test_s3_get_object_checksum[SHA256]": 0.1293253880003249, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-False]": 0.09393892899993261, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.amazonaws.com-True]": 0.09256312300021818, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-False]": 0.102580357999841, + "tests/aws/services/s3/test_s3.py::TestS3Routing::test_access_favicon_via_aws_endpoints[s3.us-west-2.amazonaws.com-True]": 0.12236299700020936, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_copy_object_with_sse_c": 0.22598157900029037, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c": 0.48236929499989856, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_multipart_upload_sse_c_validation": 0.19389456199996857, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_object_retrieval_sse_c": 0.24628459500013378, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_default_checksum_with_sse_c": 0.1810409869999603, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_lifecycle_with_sse_c": 0.18174639900007605, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_put_object_validation_sse_c": 0.20372747700002947, + "tests/aws/services/s3/test_s3.py::TestS3SSECEncryption::test_sse_c_with_versioning": 0.22496334300012677, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_crud_website_configuration": 0.10928457199975128, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_object_website_redirect_location": 0.2706537549997847, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_conditions": 0.5448250659997029, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_empty_replace_prefix": 0.42471679099980975, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_order": 0.24614988500002255, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_routing_rules_redirects": 0.1515050169996357, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_hosting": 0.5489568180000788, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_s3_static_website_index": 0.15226592899989555, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_validate_website_configuration": 0.211320490999924, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_404": 0.22795420099987496, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_http_methods": 0.14805466499979048, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_index_lookup": 0.28355622300000505, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_no_such_website": 0.14092967600004158, + "tests/aws/services/s3/test_s3.py::TestS3StaticWebsiteHosting::test_website_hosting_redirect_all": 0.30730331499989916, + "tests/aws/services/s3/test_s3.py::TestS3TerraformRawRequests::test_terraform_request_sequence": 0.05868486400004258, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_crud": 0.10077263900006983, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketAccelerateConfiguration::test_bucket_acceleration_configuration_exc": 0.12743179199992483, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_bucket_with_objects": 0.4290955030000987, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketCRUD::test_delete_versioned_bucket_with_objects": 0.4638075100003789, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms": 0.22638021800003116, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_kms_aws_managed_key": 0.2652197019997402, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_bucket_encryption_sse_s3": 0.10568463200002043, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption": 0.09043378199999097, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketEncryption::test_s3_default_bucket_encryption_exc": 0.4660247829999662, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_crud": 0.13726858200016068, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_bucket_tagging_exc": 0.08481862299981913, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_crud": 0.17983198100000664, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_exc": 0.2191362000000936, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tagging_versioned": 0.24168119000000843, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_object_tags_delete_or_overwrite_object": 0.13774571499993726, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_put_object_with_tags": 0.19905529599986949, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketObjectTagging::test_tagging_validation": 0.18314079699985086, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_bucket_ownership_controls_exc": 0.11049743099988518, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketOwnershipControls::test_crud_bucket_ownership_controls": 0.15577178599960462, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_crud": 0.11666453299994828, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketPolicy::test_bucket_policy_exc": 0.09577099499983888, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_bucket_versioning_crud": 0.15278148200013675, + "tests/aws/services/s3/test_s3_api.py::TestS3BucketVersioning::test_object_version_id_format": 0.12800867700002527, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_all_non_express": 0.08710003300029712, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_modified_non_express": 0.085744873999829, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_non_express": 0.0866245820000131, + "tests/aws/services/s3/test_s3_api.py::TestS3DeletePrecondition::test_delete_object_if_match_size_non_express": 0.08595011900001737, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration": 0.08002998399979333, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_delete_metrics_configuration_twice": 0.07777065200002653, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration": 0.0731338970001616, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_get_bucket_metrics_configuration_not_exist": 0.06392221999999492, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations": 0.08135814000002028, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_list_bucket_metrics_configurations_paginated": 0.8004593740001837, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_overwrite_bucket_metrics_configuration": 0.15233863499997824, + "tests/aws/services/s3/test_s3_api.py::TestS3MetricsConfiguration::test_put_bucket_metrics_configuration": 0.14927163299989843, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_no_copy_source_range": 0.18944730699990942, + "tests/aws/services/s3/test_s3_api.py::TestS3Multipart::test_upload_part_copy_range": 0.3353865740000401, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object": 0.09209826699998303, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_on_suspended_bucket": 0.5698317479998423, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_object_versioned": 0.5575597860004109, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects": 0.08709748800015404, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_delete_objects_versioned": 0.49000585099997807, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_range": 0.31142241299994566, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_get_object_with_version_unversioned_bucket": 0.45153762400013875, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_list_object_versions_order_unversioned": 0.4975768300000709, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectCRUD::test_put_object_on_suspended_bucket": 0.6039681629999905, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_delete_object_with_no_locking": 0.1054431200000181, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_disable_versioning_on_locked_bucket": 0.0748135120002189, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_object_lock_configuration_exc": 0.07556414200007566, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_get_put_object_lock_configuration": 0.09991970000010042, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_exc": 0.11845248999998148, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectLock::test_put_object_lock_configuration_on_existing_bucket": 0.11943818800000372, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_etag": 0.14675206799984153, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_delete": 0.13318739400006052, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put": 0.15795031100037704, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_match_with_put_identical": 0.14790060000018457, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_delete": 0.15836074899993946, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_multipart_if_none_match_with_put": 0.11224553600004583, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match": 0.12372196400019675, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_and_if_none_match_validation": 0.06900887599977068, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_validation": 0.08967691999987437, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_match_versioned_bucket": 0.16687067199995909, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match": 0.10582804099999521, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_validation": 0.08813254699998652, + "tests/aws/services/s3/test_s3_api.py::TestS3ObjectWritePrecondition::test_put_object_if_none_match_versioned_bucket": 0.14638953200005744, + "tests/aws/services/s3/test_s3_api.py::TestS3PublicAccessBlock::test_crud_public_access_block": 0.10735617099999217, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_bucket_creation": 0.42237399000009646, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_listing": 0.333181303000174, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_creation_and_read": 1.5398810340002456, + "tests/aws/services/s3/test_s3_concurrency.py::TestParallelBucketCreation::test_parallel_object_read_range": 1.4681554329997653, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_expose_headers": 0.258785386999989, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_get_no_config": 0.1126303859998643, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_no_config": 0.2000732409999273, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket": 0.1647956950000662, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_http_options_non_existent_bucket_ls_allowed": 0.09494866100021682, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_list_buckets": 0.08514889699995365, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_headers": 0.7818433739998909, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_methods": 0.7228550090001136, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_match_origins": 0.6995380230000592, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_no_config_localstack_allowed": 0.10758633399973405, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_fails_partial_origin": 0.4460621850003008, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_cors_options_match_partial_origin": 0.16351587000008294, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_delete_cors": 0.1849471769999127, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_get_cors": 0.171195914000009, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors": 0.1654940880000595, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_default_values": 0.4739850179998939, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_empty_origin": 0.16041560600001503, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_put_cors_invalid_rules": 0.1609509050001634, + "tests/aws/services/s3/test_s3_cors.py::TestS3Cors::test_s3_cors_disabled": 0.10149337400002878, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_bucket_region": 0.558815754999614, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_by_prefix_with_case_sensitivity": 0.47363691000009567, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_when_continuation_token_is_empty": 0.4685742359999949, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_continuation_token": 0.513942589999715, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListBuckets::test_list_buckets_with_max_buckets": 0.465449468000088, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multipart_uploads_marker_common_prefixes": 0.4888780119999865, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_next_marker": 0.6113252139998622, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_list_multiparts_with_prefix_and_delimiter": 0.48981898600027307, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListMultipartUploads::test_s3_list_multiparts_timestamp_precision": 0.07441657899994425, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_object_versions_pagination_common_prefixes": 0.5711505089998354, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_markers": 0.6662615090001509, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix": 0.588706669999965, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination": 0.6124455419999322, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_list_objects_versions_with_prefix_only_and_pagination_many_versions": 1.0344242979999763, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectVersions::test_s3_list_object_versions_timestamp_precision": 0.11352938799973344, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_marker_common_prefixes": 0.58078655300028, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_next_marker": 0.5177175799999532, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[%2F]": 0.45570548000023336, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[/]": 1.6455515039999682, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_list_objects_with_prefix[]": 0.44639101899997513, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_empty_marker": 0.43670093999980963, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjectsV2]": 0.08908718199973009, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjects::test_s3_list_objects_timestamp_precision[ListObjects]": 0.08674364399985279, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_common_prefixes": 0.5259039959998972, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_continuation_start_after": 0.6463906879998831, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix": 0.5147449470000538, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListObjectsV2::test_list_objects_v2_with_prefix_and_delimiter": 0.4969214129998818, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_empty_part_number_marker": 0.10117189900006451, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_pagination": 0.13926847500033546, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_list_parts_via_object_attrs_pagination": 0.2637123419999625, + "tests/aws/services/s3/test_s3_list_operations.py::TestS3ListParts::test_s3_list_parts_timestamp_precision": 0.08467818300005092, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put": 1.8448774389999016, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_in_different_region": 1.854958637000209, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_created_put_versioned": 5.191496894000011, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_object_put_acl": 1.2905730539998785, + "tests/aws/services/s3/test_s3_notifications_eventbridge.py::TestS3NotificationsToEventBridge::test_restore_object": 1.1325308400000722, + "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_by_presigned_request_via_dynamodb": 6.087013086999832, + "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_create_object_put_via_dynamodb": 2.9742858479999086, + "tests/aws/services/s3/test_s3_notifications_lambda.py::TestS3NotificationsToLambda::test_invalid_lambda_arn": 0.4393097609997767, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_not_exist": 0.37357194500032165, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_bucket_notifications_with_filter": 1.7094263309998041, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_invalid_topic_arn": 0.2519205650000913, + "tests/aws/services/s3/test_s3_notifications_sns.py::TestS3NotificationsToSns::test_object_created_put": 1.7445257680001305, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_bucket_notification_with_invalid_filter_rules": 0.2636923540001135, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_delete_objects": 0.7942340369997964, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_filter_rules_case_insensitive": 0.09785174500007088, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_invalid_sqs_arn": 0.3976670249999188, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_key_encoding": 0.6214959769997677, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_multiple_invalid_sqs_arns": 0.5995748140001069, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_notifications_with_filter": 0.747339527000122, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_and_object_removed": 0.8254153909997513, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_complete_multipart_upload": 1.9196189009999216, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_copy": 0.6693600629998855, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put": 0.6949589630003175, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_versioned": 1.0586132219996216, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_created_put_with_presigned_url_upload": 0.8925550369999655, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_put_acl": 0.8158754640003281, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_delete_event": 0.6735199639999792, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_object_tagging_put_event": 0.698435204000134, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_restore_object": 0.7857933290001711, + "tests/aws/services/s3/test_s3_notifications_sqs.py::TestS3NotificationsToSQS::test_xray_header": 1.617621208999708, + "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_lifecycle_public_access_block": 0.3185999180000181, + "tests/aws/services/s3control/test_s3control.py::TestLegacyS3Control::test_public_access_block_validations": 0.031490592999944056, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_already_exists": 0.0017500359999758075, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_bucket_not_exists": 0.0016239520000453922, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_lifecycle": 0.001655309999932797, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_name_validation": 0.0016537280002921761, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_pagination": 0.0016564819998166058, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlAccessPoint::test_access_point_public_access_block_configuration": 0.0016464730001644057, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_crud_public_access_block": 0.001669106000008469, + "tests/aws/services/s3control/test_s3control.py::TestS3ControlPublicAccessBlock::test_empty_public_access_block": 0.001677771999993638, + "tests/aws/services/scheduler/test_scheduler.py::test_list_schedules": 0.06686631499974283, + "tests/aws/services/scheduler/test_scheduler.py::test_tag_resource": 0.036072206000199, + "tests/aws/services/scheduler/test_scheduler.py::test_untag_resource": 0.031002195000155552, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[ rate(10 minutes)]": 0.014614000999927157, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31)]": 0.01551590500025668, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[at(2021-12-31T23:59:59Z)]": 0.014899096000135614, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron()]": 0.015762083000026905, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 1 * * * *)]": 0.017805988999953115, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(0 dummy ? * MON-FRI *)]": 0.017348945000094318, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(7 20 * * NOT *)]": 0.015175299000020459, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(71 8 1 * ? *)]": 0.015708113000073354, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[cron(INVALID)]": 0.01496417799990013, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate( 10 minutes )]": 0.015003669000179798, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate()]": 0.014346529000022201, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(-10 minutes)]": 0.014680000999987897, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 minutess)]": 0.015071495000029245, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 seconds)]": 0.015159478999976272, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10 years)]": 0.01485462099981305, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(10)]": 0.016196325999771943, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_invalid_schedule_expression[rate(foo minutes)]": 0.014469552000036856, + "tests/aws/services/scheduler/test_scheduler.py::tests_create_schedule_with_valid_schedule_expression": 0.11851229300032173, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times": 0.05481923199999983, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_call_lists_secrets_multiple_times_snapshots": 0.0017938890002824337, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_can_recreate_delete_secret": 0.06556400699992082, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2]": 0.08951777599986599, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name-a1b2c3-]": 0.08882193799991, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[Valid/_+=.@-Name]": 0.08925163100002464, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_and_update_secret[s-c64bdc03]": 0.11838053600035892, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets": 0.10162653400038835, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_multi_secrets_snapshot": 0.001794088999986343, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_version_from_empty_secret": 0.047708343999829594, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_create_secret_with_custom_id": 0.02699648300017543, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_delete_non_existent_secret_returns_as_if_secret_exists": 0.02590154799963784, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version": 0.9092024359999868, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_deprecated_secret_version_stage": 0.19655319900016366, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_exp_raised_on_creation_of_secret_scheduled_for_deletion": 0.048183642000140026, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_first_rotate_secret_with_missing_lambda_arn": 0.03431797599978381, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_force_delete_deleted_secret": 0.06421478800029945, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_random_exclude_characters_and_symbols": 0.016165986000032717, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value": 0.08304006000003028, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_get_secret_value_errors": 0.048585431000219614, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_custom_client_request_token_new_version_stages": 0.07591843899990636, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_duplicate_req": 0.057424936999723286, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_null_client_request_token_new_version_stages": 0.06937901799983592, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_duplicate_client_request_token": 0.05817802499973368, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_http_put_secret_value_with_non_provided_client_request_token": 0.06299049999984163, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv *?!]Name\\\\-]": 0.09083633700015525, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv Name]": 0.093348074000005, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[ Inv*Name? ]": 0.09437247599998955, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_invalid_secret_name[Inv Name]": 0.09544766399994842, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_accessed_date": 0.056198556000026656, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_last_updated_date": 0.08380531300008442, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_list_secrets_filtering": 0.1900927080000656, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[CreateSecret]": 0.028264470000067377, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[PutSecretValue]": 0.02888827200013111, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[RotateSecret]": 0.02941935200010448, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_no_client_request_token[UpdateSecret]": 0.02833861199974308, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_no_replacement": 0.214966683999819, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_non_versioning_version_stages_replacement": 0.2165260940000735, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_new_custom_client_request_token": 0.0554003960000955, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_put_secret_value_with_version_stages": 0.09912363200010077, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_resource_policy": 0.05040825299988683, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_invalid_lambda_arn": 0.2215342120000514, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_multiple_times_with_lambda_success": 2.8383321119997618, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[None]": 2.355686973999809, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_rotate_secret_with_lambda_success[True]": 2.324688379999998, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists": 0.05584425800020654, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_exists_snapshots": 0.05917191699995783, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_not_found": 0.028969280000183062, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_restore": 0.050607120999984545, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_tags": 0.13837439700000687, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_secret_version_not_found": 0.044139270999949076, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_description": 0.10428664100004426, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending": 0.23186303799957386, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle": 0.2824935940002433, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_1": 0.2871906350003428, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_2": 0.30429727400019146, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_pending_cycle_custom_stages_3": 0.26513183800011575, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_current_previous": 0.21096559700004036, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_version_stages_return_type": 0.050136750000092434, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManager::test_update_secret_with_non_provided_client_request_token": 0.04940946500005339, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access": 0.19217439799990643, + "tests/aws/services/secretsmanager/test_secretsmanager.py::TestSecretsManagerMultiAccounts::test_cross_account_access_non_default_key": 0.13416614500033575, + "tests/aws/services/ses/test_ses.py::TestSES::test_cannot_create_event_for_no_topic": 0.04290292500013493, + "tests/aws/services/ses/test_ses.py::TestSES::test_clone_receipt_rule_set": 0.8868232299998908, + "tests/aws/services/ses/test_ses.py::TestSES::test_creating_event_destination_without_configuration_set": 0.06389108700000179, + "tests/aws/services/ses/test_ses.py::TestSES::test_delete_template": 0.06662086199980877, + "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set": 0.015782585999886578, + "tests/aws/services/ses/test_ses.py::TestSES::test_deleting_non_existent_configuration_set_event_destination": 0.031336618000068484, + "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_domain": 0.013549384000043574, + "tests/aws/services/ses/test_ses.py::TestSES::test_get_identity_verification_attributes_for_email": 0.027421941000056904, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-]": 0.015611652999723447, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[-test]": 0.015073006000193345, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-]": 0.015049718999762263, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test-test_invalid_value:123]": 0.01534328599973378, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test]": 0.01664092900000469, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name:123-test_invalid_value:123]": 0.015229514000111521, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_name_len]": 0.015327576999652592, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_invalid_value_len]": 0.015308871000115687, + "tests/aws/services/ses/test_ses.py::TestSES::test_invalid_tags_send_email[test_priority_name_value]": 0.014931929000113087, + "tests/aws/services/ses/test_ses.py::TestSES::test_list_templates": 0.17673722499989708, + "tests/aws/services/ses/test_ses.py::TestSES::test_sending_to_deleted_topic": 0.45309784400024, + "tests/aws/services/ses/test_ses.py::TestSES::test_sent_message_counter": 0.12799479599993902, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_email": 1.496813693999684, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_raw_email": 1.4833770339998864, + "tests/aws/services/ses/test_ses.py::TestSES::test_ses_sns_topic_integration_send_templated_email": 1.4921407180002006, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_invalid_type": 0.032102686000143876, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Bounce]": 0.015883563999977923, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Complaint]": 0.01589595699988422, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Delivery]": 0.015432494000151564, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Bounce]": 0.04006575500011422, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Complaint]": 0.04100696499995138, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Delivery]": 0.04152758500026721, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Bounce]": 0.04162979100033226, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Complaint]": 0.04047884600004181, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Delivery]": 0.03952924500003974, + "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-a-this-marketing-campaign]": 0.01642695499981528, + "tests/aws/services/ses/test_ses.py::TestSES::test_special_tags_send_email[ses:feedback-id-b-that-campaign]": 0.017849644000079934, + "tests/aws/services/ses/test_ses.py::TestSES::test_trying_to_delete_event_destination_from_non_existent_configuration_set": 0.09593093400008001, + "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_email_can_retrospect": 1.5573608269999113, + "tests/aws/services/ses/test_ses.py::TestSESRetrospection::test_send_templated_email_can_retrospect": 0.07292720800023744, + "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[]": 0.18894436400000814, + "tests/aws/services/sns/test_sns.py::TestSNSCertEndpoint::test_cert_endpoint_host[sns.us-east-1.amazonaws.com]": 0.13702170000010483, + "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_access": 0.12438943400024982, + "tests/aws/services/sns/test_sns.py::TestSNSMultiAccounts::test_cross_account_publish_to_sqs": 0.5176263779999317, + "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_access": 0.08036255100000744, + "tests/aws/services/sns/test_sns.py::TestSNSMultiRegions::test_cross_region_delivery_sqs": 0.1513461570000345, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_create_platform_endpoint_check_idempotency": 0.0019386880001093232, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_disabled_endpoint": 0.1275312870000107, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_gcm": 0.0017306699999153352, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_publish_to_platform_endpoint_is_dispatched": 0.14866586299990558, + "tests/aws/services/sns/test_sns.py::TestSNSPlatformEndpoint::test_subscribe_platform_endpoint": 0.18251845300005698, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_empty_sns_message": 0.0947700710000845, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_message_structure_json_exc": 0.058335135000106675, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_batch_too_long_message": 0.07841305900001316, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_by_path_parameters": 0.1367394469998544, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_before_subscribe_topic": 0.1463965030000054, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_message_by_target_arn": 0.20883133400002407, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_non_existent_target": 0.035037985999906596, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_too_long_message": 0.07767933599984644, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_with_empty_subject": 0.04333701799987466, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_publish_wrong_arn_format": 0.035296158999926774, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_topic_publish_another_region": 0.05966009999974631, + "tests/aws/services/sns/test_sns.py::TestSNSPublishCrud::test_unknown_topic_publish": 0.043967749000330514, + "tests/aws/services/sns/test_sns.py::TestSNSPublishDelivery::test_delivery_lambda": 2.0711894109999776, + "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_sms_can_retrospect": 0.25374956400014526, + "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_publish_to_platform_endpoint_can_retrospect": 0.2065034109998578, + "tests/aws/services/sns/test_sns.py::TestSNSRetrospectionEndpoints::test_subscription_tokens_can_retrospect": 1.100483125999972, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms": 0.016505925999808824, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_sms_endpoint": 0.15941724199979035, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_publish_wrong_phone_format": 0.050344310999889785, + "tests/aws/services/sns/test_sns.py::TestSNSSMS::test_subscribe_sms_endpoint": 0.048042764000001625, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_create_subscriptions_with_attributes": 0.08686798500002624, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions": 0.3465201770002295, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_list_subscriptions_by_topic_pagination": 1.5366201650001585, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_not_found_error_on_set_subscription_attributes": 0.3087437729998328, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_sns_confirm_subscription_wrong_token": 0.1268712940000114, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_idempotency": 0.11105156200028432, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_protocol": 0.035124208000297585, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_subscribe_with_invalid_topic": 0.04928599900017616, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_from_non_existing_subscription": 0.09047369300014907, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_idempotency": 0.0893129940000108, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_unsubscribe_wrong_arn_format": 0.03304505400024027, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionCrud::test_validate_set_sub_attributes": 0.26979285199990954, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionFirehose::test_publish_to_firehose_with_s3": 1.2986642840000968, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[False]": 2.675191213999824, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_dlq_external_http_endpoint[True]": 2.673988290999887, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_http_subscription_response": 0.07531898899992484, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_multiple_subscriptions_http_endpoint": 1.7226615009997204, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_redrive_policy_http_subscription": 0.6557031989996176, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[False]": 1.6309575450000011, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint[True]": 1.634292295000023, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[False]": 1.6054735689997415, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_content_type[True]": 1.611645408999948, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionHttp::test_subscribe_external_http_endpoint_lambda_url_sig_validation": 2.111109881999937, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[1]": 4.203012190000209, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_publish_lambda_verify_signature[2]": 4.255926017999627, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_python_lambda_subscribe_sns_topic": 4.215071410999826, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_redrive_policy_lambda_subscription": 1.3172521540000162, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionLambda::test_sns_topic_as_lambda_dead_letter_queue": 2.3714529719998154, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_email_sender": 2.1041881000001013, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSES::test_topic_email_subscription_confirmation": 0.0642530100001295, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_attribute_raw_subscribe": 0.1491349740001624, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_empty_or_wrong_message_attributes": 0.3039134429998285, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_not_missing": 0.22527025700014747, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_attributes_prefixes": 0.18109127399998215, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_message_structure_json_to_sqs": 0.20771222400003353, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_exceptions": 0.07053003599980912, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_from_sns_to_sqs": 0.6947563439998703, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_batch_messages_without_topic": 0.034171167000067726, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns": 0.23876793499994164, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_from_sns_with_xray_propagation": 0.13951002099997822, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[1]": 0.1505640579998726, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_sqs_verify_signature[2]": 0.15008303199988404, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_publish_unicode_chars": 0.150280882999823, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[False]": 0.19863835699993615, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_redrive_policy_sqs_queue_subscription[True]": 0.20844238099994072, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_sqs_topic_subscription_confirmation": 0.07738531899985901, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_sqs_queue": 0.21651895900004092, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscribe_to_sqs_with_queue_url": 0.048073442000486466, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQS::test_subscription_after_failure_to_deliver": 1.5263521460001357, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[False]": 0.28801167300002817, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_fifo_topic_to_regular_sqs[True]": 0.2820340479997867, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[False]": 1.1935328389999995, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs[True]": 1.1826266470000064, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_message_to_fifo_sqs_ordering": 2.658634333000009, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[False]": 3.6058628499997667, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_batch_messages_from_fifo_topic_to_fifo_queue[True]": 3.6176721779997933, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[False]": 1.5836872719999064, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_fifo_messages_to_dlq[True]": 1.5727019979999568, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_deduplication_on_topic_level": 1.67330695700025, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[False]": 0.28680674899987935, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_topic_to_sqs_queue_no_content_dedup[True]": 0.2774751459999152, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_publish_to_fifo_with_target_arn": 0.039395489999833444, + "tests/aws/services/sns/test_sns.py::TestSNSSubscriptionSQSFifo::test_validations_for_fifo": 0.23978177500021047, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_check_idempotency": 0.09282168900017496, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_duplicate_topic_with_more_tags": 0.03585096500000873, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_after_delete_with_new_tags": 0.057167231999983414, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_test_arn": 1.6374445740000283, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_create_topic_with_attributes": 0.2716384489997381, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_delete_topic_idempotency": 0.0632574520000162, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_tags": 0.09143210599995655, + "tests/aws/services/sns/test_sns.py::TestSNSTopicCrud::test_topic_delivery_policy_crud": 0.0017387579998739966, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy": 0.322334673000114, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_exists_filter_policy_attributes_array": 4.2808627349998005, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyAttributes::test_filter_policy": 5.328931239999974, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_empty_array_payload": 0.17367581299981794, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_for_batch": 3.4023604550000073, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_ip_address_condition": 0.3456397930001458, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_large_complex_payload": 0.19139297900005658, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[False]": 5.339477560999967, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body[True]": 5.338176317000034, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_attributes": 0.6210448149997774, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_array_of_object_attributes": 0.3511479030003102, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_dot_attribute": 5.58802273099991, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyBody::test_filter_policy_on_message_body_or_attribute": 0.8845163729997694, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity": 0.05520270400006666, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_policy_complexity_with_or": 0.05895180000015898, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy": 0.12688766500014026, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_exists_operator": 0.11923605900005896, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_nested_anything_but_operator": 0.1649367920001623, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_numeric_operator": 0.22147131900010208, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyConditions::test_validate_policy_string_operators": 0.22627250500022456, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_set_subscription_filter_policy_scope": 0.12763074200029223, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property": 0.10921357799998077, + "tests/aws/services/sns/test_sns_filter_policy.py::TestSNSFilterPolicyCrud::test_sub_filter_policy_nested_property_constraints": 0.1818112010000732, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[domain]": 0.09783750799988411, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[path]": 0.09754238000004989, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_access[standard]": 0.09832583499974135, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[domain]": 0.03232826099974773, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[path]": 0.03143026800080406, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_cross_account_get_queue_url[standard]": 0.033724359999723674, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs]": 0.0939477629999601, + "tests/aws/services/sqs/test_sqs.py::TestSQSMultiAccounts::test_delete_queue_multi_account[sqs_query]": 0.0917459429997507, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs]": 3.1393625870000506, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_approximate_number_of_messages_delayed[sqs_query]": 3.13762107499997, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs]": 0.10627448300010656, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_aws_trace_header_propagation[sqs_query]": 0.10652091800011476, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs]": 0.18749180999998316, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_batch_send_with_invalid_char_should_succeed[sqs_query]": 0.1957790189997013, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs]": 2.1014917579998382, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_after_visibility_timeout_expiration[sqs_query]": 2.1021476820001226, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs]": 0.6608746569997948, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_batch_with_too_large_batch[sqs_query]": 0.6783476670000255, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs]": 0.10182768100003159, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_message_visibility_not_permanent[sqs_query]": 0.10598504800009323, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs]": 0.09514213299962648, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_change_visibility_on_deleted_message_raises_invalid_parameter_value[sqs_query]": 0.09702010599994537, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs]": 0.06477376499969978, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_send_to_fifo_queue[sqs_query]": 0.06616217999999208, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs]": 0.08434016000001066, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_and_update_queue_attributes[sqs_query]": 0.08765784000024723, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs]": 0.14181966000001012, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_different_attributes_raises_error[sqs_query]": 0.1417360110001482, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_fifo_queue_with_same_attributes_is_idempotent": 0.03897249800024838, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs]": 0.08968462999973781, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_internal_attributes_changes_works[sqs_query]": 0.0879101030000129, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs]": 0.0016947019998951873, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_modified_attributes[sqs_query]": 0.0016530950001651945, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs]": 0.1193835130000025, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_after_send[sqs_query]": 0.11898866200021985, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs]": 0.03220381199980693, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_and_get_attributes[sqs_query]": 0.032931259999941176, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs]": 0.03634195100016768, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted[sqs_query]": 0.037439610000092216, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs]": 1.5541357189997598, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_cache[sqs_query]": 1.5560162709998622, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs]": 0.045494491000226844, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_recently_deleted_can_be_disabled[sqs_query]": 0.044720315999938975, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs]": 0.0017527909999444091, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_arguments_works_with_modified_attributes[sqs_query]": 0.0016658079998705944, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_default_attributes_is_idempotent": 0.03959557300004235, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs]": 0.20390825200001927, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_different_attributes_raises_exception[sqs_query]": 0.19905578299994886, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_same_attributes_is_idempotent": 0.0404679999999189, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs]": 0.0291416689997277, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_with_tags[sqs_query]": 0.031076513000016348, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_queue_without_attributes_is_idempotent": 0.03711632600015946, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs]": 0.08102237699995385, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_create_standard_queue_with_fifo_attribute_raises_error[sqs_query]": 0.08039295599996876, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs]": 0.00174301199990623, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_chain[sqs_query]": 0.0016282080000564747, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_config": 0.03865213199992468, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs]": 0.0017549839999446704, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_execution_lambda_mapping_preserves_id[sqs_query]": 0.001640479999650779, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs]": 0.06195007699989219, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_list_sources[sqs_query]": 0.06236539999986235, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs]": 0.13554719499984458, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_max_receive_count[sqs_query]": 0.1310280119998879, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_message_attributes": 0.7563028629999735, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs]": 0.1739812239998173, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_dead_letter_queue_with_fifo_and_content_based_deduplication[sqs_query]": 0.17822838999995838, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs]": 0.0017485820003457775, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_deduplication_interval[sqs_query]": 0.0016986089999591059, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs]": 1.1283156260001306, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_after_visibility_timeout[sqs_query]": 1.131211070999825, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs]": 0.0017530510001506627, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_from_lambda[sqs_query]": 0.001649197000233471, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-]": 0.1858439670002099, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-invalid:id]": 0.07108344699986446, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.0710004720001507, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-]": 0.07109615100011979, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-invalid:id]": 0.07100436099995022, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_invalid_msg_id[sqs_query-testLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongIdtestLongId]": 0.07344531600006121, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs]": 0.6566862459999356, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_batch_with_too_large_batch[sqs_query]": 0.6625412919997871, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs]": 0.13287170400008108, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_deletes_with_change_visibility_timeout[sqs_query]": 0.13347027200006778, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs]": 0.11241575699978057, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_deleted_receipt_handle[sqs_query]": 0.11754002300040156, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs]": 0.034206299999823386, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_delete_message_with_illegal_receipt_handle[sqs_query]": 0.033014806999744906, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_disallow_queue_name_with_slashes": 0.0017374929998368316, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs]": 6.175611156999594, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_extend_message_visibility_timeout_set_in_queue[sqs_query]": 6.998128298999973, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs]": 0.1164166319999822, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_endpoint[sqs_query]": 0.06582403899983547, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_host_via_header_complete_message_lifecycle": 0.09059035900008894, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_external_hostname_via_host_header": 0.03446268600032454, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs]": 0.09177747800049474, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fair_queue_with_message_group_id[sqs_query]": 0.0882318869998926, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs]": 0.2542879099996753, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_approx_number_of_messages[sqs_query]": 0.2540186359999552, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs]": 0.3319482249999055, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_high_throughput_after_creation[sqs_query]": 0.3353008080002837, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs]": 0.2318468300004497, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_change_to_regular_throughput_after_creation[sqs_query]": 0.234883157999775, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs]": 1.102021681999986, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_content_based_message_deduplication_arrives_once[sqs_query]": 1.1001030789996094, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-False]": 1.1560234930000206, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs-True]": 1.1511007889998837, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-False]": 1.1575869129999319, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_arrives_once_after_delete[sqs_query-True]": 1.1398446599998806, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-False]": 1.1367069289999563, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs-True]": 1.1377405289999842, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-False]": 1.1469555639998816, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_deduplication_not_on_message_group_id[sqs_query-True]": 1.1414682189999894, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs]": 1.1756366839999828, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_after_visibility_timeout[sqs_query]": 1.1797505890001503, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs]": 0.0017226840000148513, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_delete_message_with_expired_receipt_handle[sqs_query]": 0.0016661089998706302, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs]": 0.17524220399991464, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_empty_message_groups_added_back_to_queue[sqs_query]": 0.17955981000000065, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs]": 0.1609178549997523, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_high_throughput_ordering[sqs_query]": 0.16065686399997503, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs]": 0.16069993299993257, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_attributes[sqs_query]": 0.16265235799983202, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility": 2.1318445279996467, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs]": 2.1154009420001785, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_change_message_visibility[sqs_query]": 2.133017697000014, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs]": 0.2789768360000835, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_delete[sqs_query]": 0.29254459599974325, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs]": 0.2667155270000876, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_partial_delete[sqs_query]": 0.26805080500025724, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs]": 1.4322617839998202, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_message_group_visibility_after_terminate_visibility_timeout[sqs_query]": 0.14140474500027267, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs]": 2.118401382000229, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_messages_in_order_after_timeout[sqs_query]": 2.11047787099983, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_requires_suffix": 0.015953265999769428, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs]": 4.1162861719999455, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_on_queue_works[sqs_query]": 4.115131692999967, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs]": 0.15881160800017824, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs_query]": 0.15925185000037345, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs]": 4.111378885000249, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs_query]": 4.114528785000175, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs]": 0.2458133809998344, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_multiple_messages_multiple_single_receives[sqs_query]": 0.25098753900010706, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs]": 0.13841478200015445, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_group_id_ordering[sqs_query]": 0.13798832899988156, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs]": 2.1900150050000775, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_visibility_timeout_shared_in_group[sqs_query]": 2.2034836050002014, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs]": 0.17662989099972037, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_receive_message_with_zero_visibility_timeout[sqs_query]": 0.18262592599990057, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs]": 0.09759775299994544, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_sequence_number_increases[sqs_query]": 0.1006142369999452, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": 0.08534771199992974, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs_query]": 0.08762399200031723, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_list_queues_with_query_auth": 0.022464847000264854, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs]": 0.030698834999839164, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_contains_localstack_host[sqs_query]": 0.04070833000037055, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[domain]": 0.05158792000020185, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[path]": 0.05197999299980438, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_queue_url_multi_region[standard]": 0.05230423700004394, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs]": 0.06150286499996582, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_get_specific_queue_attribute_response[sqs_query]": 0.05994680599997082, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_inflight_message_requeue": 4.601242724999793, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs]": 0.14077715400026136, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_batch_id[sqs_query]": 0.1407836129997122, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_dead_letter_arn_rejected_before_lookup": 0.001725118999956976, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs]": 0.032453457999963575, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_receipt_handle_should_return_error_message[sqs_query]": 0.03343645099971582, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs]": 0.03238081500012413, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_invalid_string_attributes_cause_invalid_parameter_value_error[sqs_query]": 0.030926454999871567, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs]": 0.03717541800028812, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queue_tags[sqs_query]": 0.03642958100022042, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues": 0.09796003800011022, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_domain": 0.06330248199947164, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_with_endpoint_strategy_standard": 0.06602199100007056, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_multi_region_without_endpoint_strategy": 0.0724629129999812, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_list_queues_pagination": 0.27640914899984637, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[\"{\\\\\"foo\\\\\": \\\\\"ba\\\\rr\\\\\"}\"]": 0.08093815600000198, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_marker_serialization_json_protocol[{\"foo\": \"ba\\rr\", \"foo2\": \"ba"r"\"}]": 0.08061865400009083, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[empty]": 0.06912328499993237, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[spaces]": 0.06849254600001586, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_invalid[too_long]": 0.07070243299995127, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_success": 0.04901426299988998, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention": 3.079655788000082, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_fifo": 3.0743464080001104, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_retention_with_inflight": 5.615479370999992, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs]": 0.12726062699994145, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_system_attribute_names_with_attribute_names[sqs_query]": 0.12463132499988205, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs]": 0.06533414500017898, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_attributes_should_be_enqueued[sqs_query]": 0.06623371800014866, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs]": 0.06072377099985715, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_with_carriage_return[sqs_query]": 0.070069019000357, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_non_existent_queue": 0.1726600219994907, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs]": 0.24304738100022405, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_fifo_requires_deduplicationid_group_id[sqs_query]": 0.2411243579999791, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs]": 0.051338586999918334, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_posting_to_queue_via_queue_name[sqs_query]": 0.050975614000208225, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs]": 0.09428283299985196, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message[sqs_query]": 0.09566848600002231, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs]": 0.19681186299976616, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_publish_get_delete_message_batch[sqs_query]": 0.21819292600002882, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs]": 1.2458795400000326, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue[sqs_query]": 1.2124546029999692, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs]": 0.11047140899972874, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_clears_fifo_deduplication_cache[sqs_query]": 0.12356113899932097, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs]": 3.142380587000389, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_delayed_messages[sqs_query]": 3.1289368560001094, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs]": 4.23093155100014, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_purge_queue_deletes_inflight_messages[sqs_query]": 4.311182651999616, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs]": 0.029771908999919106, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_queue_list_nonexistent_tags[sqs_query]": 0.029715603999875384, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs]": 1.716797415000201, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_after_visibility_timeout[sqs_query]": 1.9987264310000228, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs]": 1.0930804609999996, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_empty_queue[sqs_query]": 1.0953330810000352, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs]": 0.23112154599994028, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attribute_names_filters[sqs_query]": 0.23893329099973926, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs]": 0.0674568389997603, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_attributes_timestamp_types[sqs_query]": 0.06846805599980144, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs]": 0.2677712159998009, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_attribute_names_filters[sqs_query]": 0.27364686800001436, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs]": 0.1593250020000596, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_message_system_attribute_names_filters[sqs_query]": 0.16299368800036973, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs]": 0.0955024439999761, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_wait_time_seconds_and_max_number_of_messages_does_not_block[sqs_query]": 0.09837166099987371, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs]": 0.09496301800004403, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_message_with_visibility_timeout_updates_timeout[sqs_query]": 0.09662560399988251, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs]": 0.09692435199985994, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_receive_terminate_visibility_timeout[sqs_query]": 0.10021483099990292, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs]": 0.0018512940000618983, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_redrive_policy_attribute_validity[sqs_query]": 0.0016933199999584758, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs]": 2.07979463699985, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_remove_message_with_old_receipt_handle[sqs_query]": 2.080525287000455, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_message_size": 0.22973294700022961, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs]": 0.14065183500019884, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_deduplication_id_for_fifo_queue[sqs_query]": 0.1419881939998504, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs]": 0.1422881719997804, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_missing_message_group_id_for_fifo_queue[sqs_query]": 0.14158159299972795, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs]": 0.10753989299996647, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_batch_receive_multiple[sqs_query]": 0.10946048000027986, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs]": 1.2919750530002148, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_delay_and_wait_time[sqs_query]": 1.9994196699997246, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs]": 0.14096988100004637, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_empty_message[sqs_query]": 0.145096660000263, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs]": 0.11517063700034669, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch[sqs_query]": 0.11684964100004436, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs]": 0.030457655000191153, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_empty_list[sqs_query]": 0.03315287999976135, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs]": 0.1467931529998623, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents[sqs_query]": 0.14956966399995508, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs]": 0.11406526899986602, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_batch_with_oversized_contents_with_updated_maximum_message_size[sqs_query]": 0.11588921899965499, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[empty]": 0.09195043199997599, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[spaces]": 0.09133494299999256, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_to_standard_queue_with_invalid_message_group_id[too_long]": 0.07257359300024291, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs]": 0.06396383100013736, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_attributes[sqs_query]": 0.06632503400010137, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs]": 0.10607935000007274, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_binary_attributes[sqs_query]": 0.10654537400000663, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs]": 0.06530694900015988, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_delay_0_works_for_fifo[sqs_query]": 0.06604753699980392, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs]": 0.14277940499982833, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_empty_string_attribute[sqs_query]": 0.143571940999891, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs]": 0.0016862160000528092, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_fifo_parameters[sqs_query]": 0.0016711780001514853, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs]": 0.030566817999897467, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_payload_characters[sqs_query]": 0.03048769399970297, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs]": 0.13555689999998322, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_invalid_string_attributes[sqs_query]": 0.14082824100000835, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs]": 0.17482401799975378, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_message_with_updated_maximum_message_size[sqs_query]": 0.1736371380000037, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs]": 0.14930358599985993, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_oversized_message[sqs_query]": 0.1557271359997685, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs]": 0.1675370779998957, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_max_number_of_messages[sqs_query]": 0.16667778099986208, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs]": 0.06483472300010362, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message[sqs_query]": 0.06776787799981321, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs]": 0.06364796400021078, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_encoded_content[sqs_query]": 0.06344601300042996, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_message_multiple_queues": 0.09434092200012856, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs]": 0.23320687700015696, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_send_receive_wait_time_seconds[sqs_query]": 0.2355102380001881, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs]": 0.08115899500035084, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sent_message_retains_attributes_after_receive[sqs_query]": 0.08086315700006708, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs]": 0.08762207900008434, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sequence_number[sqs_query]": 0.08940339099990524, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs]": 0.0622452710001653, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_queue_policy[sqs_query]": 0.06766719399979593, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs]": 0.07471047600029124, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_empty_redrive_policy[sqs_query]": 0.07334283399995911, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs]": 0.044533220999710466, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_queue_policy[sqs_query]": 0.045418487999995705, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs]": 0.24098988700006885, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_fifo[sqs_query]": 0.24169332700012092, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs]": 0.2210830320000241, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_set_unsupported_attribute_standard[sqs_query]": 0.2220762699998886, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs]": 0.16078478100007487, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_message_group_scope_no_throughput_setting[sqs_query]": 0.16410713499999474, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs]": 0.15884505599979093, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_fifo_same_dedup_id_different_message_groups[sqs_query]": 0.162316176999866, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs]": 0.25023639099981665, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sqs_permission_lifecycle[sqs_query]": 0.252970978000576, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs]": 0.0017702439999993658, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_kms_and_sqs_are_mutually_exclusive[sqs_query]": 0.0017476699995313538, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs]": 0.1082397380000657, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_sse_queue_attributes[sqs_query]": 0.1081493100004991, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_standard_queue_cannot_have_fifo_suffix": 0.01434963300016534, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs]": 0.16192369300006249, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_successive_purge_calls_fail[sqs_query]": 0.1654855170004339, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs]": 0.07595017299991014, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_system_attributes_have_no_effect_on_attr_md5[sqs_query]": 0.07841271400025107, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs]": 0.043855270999983986, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_queue_overwrites_existing_tag[sqs_query]": 0.04463187900000776, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs]": 0.10651429800009282, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tag_untag_queue[sqs_query]": 0.10920287299995834, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs]": 0.03664223200007655, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_tags_case_sensitive[sqs_query]": 0.03871664200005398, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs]": 0.10085900000012771, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_terminate_visibility_timeout_after_receive[sqs_query]": 0.10354559000006702, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs]": 0.13995797500001572, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_too_many_entries_in_batch_request[sqs_query]": 0.14561698299985437, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs]": 0.044074069000089366, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_untag_queue_ignores_non_existing_tag[sqs_query]": 0.04463743999986036, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs]": 1.0647604310001952, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_queue_attribute_waits_correctly[sqs_query]": 1.0642365930002597, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs]": 1.066442337000126, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_wait_time_seconds_waits_correctly[sqs_query]": 1.0682329620001383, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[domain]": 0.12294029699978637, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[off]": 0.12342318399987562, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[path]": 0.12540290400011145, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_endpoint_strategy_with_multi_region[standard]": 0.17894714799967915, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_create_queue_fails": 0.03233061999981146, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[domain]": 0.05384767199939233, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[path]": 0.0666227209999306, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_delete_queue[standard]": 0.05825744399953692, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails": 0.031226930000229913, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_list_queues_fails_json_format": 0.0018641880001268873, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs]": 0.053916984000352386, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_on_deleted_queue_fails[sqs_query]": 0.056714966000527056, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_all": 0.05326804399965113, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_json_format": 0.0017785990003176266, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_of_fifo_queue": 0.046707964999313845, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_invalid_arg_returns_error": 0.04593975199986744, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_with_query_args": 0.04892011399988405, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[domain]": 0.043201055000281485, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[path]": 0.054134271000293666, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_attributes_works_without_authparams[standard]": 0.04340762600031667, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[domain]": 0.05544545000020662, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[path]": 0.054139952000241465, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_work_for_different_queue[standard]": 0.05356674899985592, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[domain]": 0.04026055800022732, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[path]": 0.040036969000084355, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_queue_url_works_for_same_queue[standard]": 0.04150588299989977, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_send_and_receive_messages": 0.1257204269995782, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_json_format_returns_returns_xml": 0.031463221999729285, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_get_without_query_returns_unknown_operation": 0.03291069599981711, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_invalid_action_raises_exception": 0.04377468399934514, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_overwrite_queue_url_in_params": 0.05890972499992131, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_queue_url_format_path_strategy": 0.025909269999374374, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_send_message_via_queue_url_with_json_protocol": 1.0894375080001737, + "tests/aws/services/sqs/test_sqs.py::TestSqsQueryApi::test_valid_action_with_missing_parameter_raises_exception": 0.03842580299988185, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-domain]": 0.1058544620000248, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-path]": 0.10628666100001283, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[json-standard]": 0.11150053900018975, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-domain]": 0.10299661000090055, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-path]": 0.10912125200002265, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_fifo_list_messages_as_botocore_endpoint_url[query-standard]": 0.10613224700000501, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-domain]": 0.07972841099990546, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-path]": 0.08303337899997132, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[json-standard]": 0.08828805300026943, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-domain]": 0.08497584700035077, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-path]": 0.08255777099975603, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_botocore_endpoint_url[query-standard]": 0.09034466999992219, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[domain]": 0.0794393740002306, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[path]": 0.07794958600015889, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_as_json[standard]": 0.07941269500042836, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[domain]": 0.1022264649996032, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[path]": 0.10578971899985845, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_has_no_side_effects[standard]": 0.10046537699963665, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[domain]": 0.11073597699987658, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[path]": 0.11024945800045316, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_delayed_messages[standard]": 0.1099553859999105, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-domain]": 0.031129017999774078, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-path]": 0.03149517300016669, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[json-standard]": 0.0330512649998127, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-domain]": 0.030454440000085015, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-path]": 0.030496278999180504, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_action_raises_error[query-standard]": 0.0316524150002806, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[domain]": 0.02031825000040044, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[path]": 0.019547207000414346, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invalid_queue_url[standard]": 0.021758015000159503, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[domain]": 0.12850700500030143, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[path]": 0.1292240689999744, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_invisible_messages[standard]": 0.12628653699994175, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[domain]": 0.0241992329997629, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[path]": 0.024764414000401302, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_non_existent_queue[standard]": 0.02436604099966644, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[domain]": 0.08550624000008611, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[path]": 0.08858581100002993, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_with_queue_url_in_path[standard]": 0.08335174400008327, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[domain]": 0.018638084000485833, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[path]": 0.01874764900003356, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsDeveloperEndpoints::test_list_messages_without_queue_url[standard]": 0.01990627100030906, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsOverrideHeaders::test_receive_message_override_max_number_of_messages": 0.4258194680005545, + "tests/aws/services/sqs/test_sqs_backdoor.py::TestSqsOverrideHeaders::test_receive_message_override_message_wait_time_seconds": 25.36306605300024, + "tests/aws/services/sqs/test_sqs_move_task.py::test_basic_move_task_workflow": 1.8236006470006032, + "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_source_arn_in_task_handle": 0.053359607999937, + "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_handle": 0.056346283000038966, + "tests/aws/services/sqs/test_sqs_move_task.py::test_cancel_with_invalid_task_id_in_task_handle": 0.07595860600031301, + "tests/aws/services/sqs/test_sqs_move_task.py::test_destination_needs_to_exist": 0.11496627499991519, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_cancel": 1.84567243399988, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_delete_destination_queue_while_running": 1.8772769509996579, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_with_throughput_limit": 3.3992769699993914, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_default_destination": 1.8079318989998683, + "tests/aws/services/sqs/test_sqs_move_task.py::test_move_task_workflow_with_multiple_sources_as_default_destination": 2.508253336999587, + "tests/aws/services/sqs/test_sqs_move_task.py::test_source_needs_redrive_policy": 0.0995014150003044, + "tests/aws/services/sqs/test_sqs_move_task.py::test_start_multiple_move_tasks": 0.7064428459998453, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_describe_parameters": 0.016459307999411976, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_maintenance_window": 0.015634809999482968, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_inexistent_secret": 0.035415566000210674, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameter_by_arn": 0.06187413900033789, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_and_secrets": 0.1219743390001895, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_parameters_by_path_and_filter_by_labels": 0.0636830329995064, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_get_secret_parameter": 0.07009276200005843, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[///b//c]": 0.06552879499986375, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_hierarchical_parameter[/b/c]": 0.06530791700060945, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_parameters_with_path": 0.1599483800005146, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_put_parameters": 0.08171507999941241, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[domain]": 0.1204813849999482, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[path]": 0.12036659700015662, + "tests/aws/services/ssm/test_ssm.py::TestSSM::test_trigger_event_on_systems_manager_change[standard]": 0.12215860399965095, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task": 2.2025921490003384, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_failure": 2.0066568579995874, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_no_worker_name": 1.9381670290003967, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_on_deleted": 0.4542757679996612, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_start_timeout": 5.696678518000226, + "tests/aws/services/stepfunctions/v2/activities/test_activities.py::TestActivities::test_activity_task_with_heartbeat": 7.303554840000288, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY]": 2.239447876000213, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EMPTY_GLOBAL_QL_JSONATA]": 2.3272254160001467, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_EXPRESSION]": 6.546134073000303, + "tests/aws/services/stepfunctions/v2/arguments/test_arguments.py::TestArgumentsBase::test_base_cases[BASE_LAMBDA_LITERALS]": 2.4181482770000002, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_FALSE]": 0.7901219069998433, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_assign_in_choice[CONDITION_TRUE]": 1.2013635299999805, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_CONSTANT_LITERALS]": 1.1462004319996595, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_EMPTY]": 0.715632094999819, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_PATHS]": 1.003259407999849, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_SCOPE_MAP]": 1.4044351980005558, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_cases[BASE_VAR]": 1.3333726960004242, + "tests/aws/services/stepfunctions/v2/assign/test_assign_base.py::TestAssignBase::test_base_parallel_cases[BASE_SCOPE_PARALLEL]": 5.544530633000022, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 2.456013195999958, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_PARAMETERS]": 0.9850257279999823, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_from_value[BASE_ASSIGN_FROM_RESULT]": 1.0178888049999841, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_catch_state": 18.15633716399998, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[CORRECT]": 1.0161739410000337, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_choice_state[INCORRECT]": 0.9603733650001232, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_assign_in_wait_state": 0.7215536319999956, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_CHOICE]": 1.0397190800000544, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_FAIL]": 0.9647792490000029, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INPUTPATH]": 1.023706199000003, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.2502043800001275, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 2.1620165280000947, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_OUTPUTPATH]": 0.9730342230000133, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_PARAMETERS]": 1.6136183979998577, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_assign[BASE_REFERENCE_IN_WAIT]": 1.013226900999939, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.957210449999934, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.2337147010000535, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 1.2559234330001345, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 0.9684961239998984, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 1.0934986539999727, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 1.1460582290000048, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_reference_in_map_state_max_items_path[MAP_STATE_REFERENCE_IN_MAX_PER_BATCH_PATH]": 0.00185462899992217, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_state_assign_evaluation_order[BASE_EVALUATION_ORDER_PASS_STATE]": 0.002395953999894118, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS]": 0.00251373500009322, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ARGUMENTS_FIELD]": 0.002787219999959234, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_ASSIGN]": 1.226541417999897, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT]": 1.2558257480000066, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_FIELD]": 1.25556821400005, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_undefined_reference[BASE_UNDEFINED_OUTPUT_MULTIPLE_STATES]": 1.2758664409999483, + "tests/aws/services/stepfunctions/v2/assign/test_assign_reference_variables.py::TestAssignReferenceVariables::test_variables_in_lambda_task[BASE_ASSIGN_FROM_LAMBDA_TASK_RESULT]": 3.3090618830000267, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_decl_version_1_0": 0.4598413809999329, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_base": 2.817872684000008, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_event_bridge_events_failure": 0.001962651999974696, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_execution_dateformat": 0.40106061699998463, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[0]]": 0.7006309339999461, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_access[$.items[10]]": 0.6867378160000044, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[*]]": 0.6654430309999952, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5].itemValue]": 0.6783285219999016, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:5]]": 0.689184657999931, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[1:]]": 0.6780039239999951, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.item.items[:1]]": 0.6666309199999887, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*].itemValue]": 0.6906795349998447, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[*]]": 0.6806551899999249, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:].itemValue]": 0.6923616320000292, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[1:]]": 0.6649481929998728, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1].itemValue]": 0.6917752890000202, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$.items[:1]]": 0.6685090330000776, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_json_path_array_wildcard_or_slice_with_no_input[$[*]]": 0.694629415999998, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_query_context_object_values": 1.635330741999951, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail": 0.6114034239999455, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_empty": 0.596272751000015, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_intrinsic": 0.6891333710000254, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_fail_path": 0.6852056000000175, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path": 0.0016515990000698366, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_regex_json_path_base": 0.6542213359998641, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result": 0.4512605469999471, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_jsonpaths": 0.6659976230000666, + "tests/aws/services/stepfunctions/v2/base/test_base.py::TestSnfBase::test_state_pass_result_null_input_output_paths": 1.3863451009999608, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1.5]": 0.6678089529999625, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[-1]": 0.6864859629999955, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[0]": 0.651666653999996, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1.5]": 0.6893752400000039, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_base_wait_seconds_path[1]": 1.5003757209999549, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24855]": 0.0017181120000486771, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_timestamp_too_far_in_future_boundary[24856]": 0.0015810249999503867, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000Z]": 0.662521235999975, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.000000]": 0.6628516469999113, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[.00Z]": 0.6769856639999716, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[Z]": 0.6877339230001098, + "tests/aws/services/stepfunctions/v2/base/test_wait.py::TestSfnWait::test_wait_timestamppath[]": 0.6654866040000798, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_executions_and_heartbeat_notifications": 0.0017500019999943106, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_multiple_heartbeat_notifications": 0.0026311959999247847, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sns_publish_wait_for_task_token": 1.548599232999777, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_PARALLEL_WAIT_FOR_TASK_TOKEN]": 0.01059930099995654, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_tok_no_error_field[SQS_WAIT_FOR_TASK_TOKEN_CATCH]": 1.9271682119999696, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_failure_in_wait_for_task_token": 2.4893234190001294, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_tok_with_heartbeat": 7.63766406700006, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token": 2.5560979970000517, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_call_chain": 5.384145598999908, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_no_token_parameter": 5.776784422999981, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sqs_wait_for_task_token_timeout": 6.457648446999997, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync": 1.3604159129998834, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync2": 1.1170071469999812, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_failure": 1.0950301390000732, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_start_execution_sync_delegate_timeout": 7.611491364000017, + "tests/aws/services/stepfunctions/v2/callback/test_callback.py::TestCallback::test_sync_with_task_token": 3.0422179679999317, + "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals": 14.681928356999947, + "tests/aws/services/stepfunctions/v2/choice_operators/test_boolean_equals.py::TestBooleanEquals::test_boolean_equals_path": 15.500212131999888, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_boolean": 14.449849345000075, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_null": 15.371293147999836, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_numeric": 14.756201206000014, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_present": 14.452014894000058, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_string": 14.973388182999997, + "tests/aws/services/stepfunctions/v2/choice_operators/test_is_operators.py::TestIsOperators::test_is_timestamp": 0.00422801900003833, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals": 21.250699331000078, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_equals_path": 23.025873045000026, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than": 2.6289043880000236, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals": 2.5471024560000615, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_equals_path": 2.5778029219999326, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_greater_than_path": 2.584370337999985, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than": 2.6331509609999557, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals": 2.596382725000012, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_equals_path": 2.6375531609999143, + "tests/aws/services/stepfunctions/v2/choice_operators/test_numeric.py::TestNumerics::test_numeric_less_than_path": 2.6650619980000556, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals": 6.263363615999992, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_equals_path": 1.4630063770001698, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than": 2.642512907999958, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals": 1.5057728370001087, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_equals_path": 1.4931736349999483, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_greater_than_path": 1.8458909920000224, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than": 1.4690585900000315, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals": 1.4174395599999343, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_equals_path": 1.3845795799999223, + "tests/aws/services/stepfunctions/v2/choice_operators/test_string_operators.py::TestStrings::test_string_less_than_path": 1.3963172490001625, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals": 6.92434126899991, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_equals_path": 1.4246245240000235, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than": 1.486413964999997, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals": 1.4198465350000333, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_equals_path": 0.6796717540000827, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_greater_than_path": 0.7019121529999666, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than": 1.5086678049999591, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals": 1.4311500179999257, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_equals_path": 1.3423975029999156, + "tests/aws/services/stepfunctions/v2/choice_operators/test_timestamp_operators.py::TestTimestamps::test_timestamp_less_than_path": 0.7174882550000348, + "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comment_in_parameters": 0.47531331899995166, + "tests/aws/services/stepfunctions/v2/comments/test_comments.py::TestComments::test_comments_as_per_docs": 7.563463656999943, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_error_cause_path": 1.009578237000028, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$.Execution.Input]": 1.0032791149999412, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_input_path[$$]": 0.7945211590000554, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$.Execution.Input]": 1.0048055779999459, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_output_path[$$]": 0.7924935110000888, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_result_selector": 2.679184011999837, + "tests/aws/services/stepfunctions/v2/context_object/test_context_object.py::TestSnfBase::test_variable": 1.0181612769998765, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_lambda_task": 2.416481987999987, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke": 2.508915974000047, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_service_lambda_invoke_retry": 5.910621027999923, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_INTRINSIC]": 1.5815793240000176, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_JSONATA]": 1.6344443940000701, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH]": 1.6239547750000156, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_PATH_CONTEXT]": 2.336414098999967, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_cross_account_states_start_sync_execution[SFN_START_EXECUTION_SYNC_ROLE_ARN_VARIABLE]": 1.6328753099999176, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[EMPTY_CREDENTIALS]": 0.917282149000016, + "tests/aws/services/stepfunctions/v2/credentials/test_credentials_base.py::TestCredentialsBase::test_invalid_credentials_field[INVALID_CREDENTIALS_FIELD]": 0.9329226400000152, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_invalid_param": 0.0019636440000567745, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_dynamodb_put_item_no_such_table": 2.977503529000046, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_invalid_secret_name": 0.8381341110000449, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_no_such_bucket": 0.7487852140001223, + "tests/aws/services/stepfunctions/v2/error_handling/test_aws_sdk.py::TestAwsSdk::test_s3_no_such_key": 0.7600843169999507, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.315783844999828, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_service_task_lambada_data_limit_exceeded_on_large_utf8_response": 2.3258397600001217, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_start_large_input": 5.402653756000063, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_catch_state_all_data_limit_exceeded_on_large_utf8_response": 2.28911656199989, + "tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py::TestStatesErrors::test_task_lambda_data_limit_exceeded_on_large_utf8_response": 3.1232344259999536, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function": 2.461626869999918, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_no_such_function_catch": 2.4494925909999665, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_custom_exception": 2.23189615900003, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception": 2.450958947999993, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_lambda.py::TestTaskLambda::test_raise_exception_catch": 2.487495405000118, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_invalid_param": 0.8000227970001106, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_invalid_table_name": 0.8821819219999725, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_dynamodb.py::TestTaskServiceDynamoDB::test_put_item_no_such_table": 0.7604338189998998, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_invoke_timeout": 6.741741648000129, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function": 1.8268623269998443, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_no_such_function_catch": 1.9366193619999876, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_custom_exception": 2.3275826010000173, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception": 2.3511853260000635, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch": 2.6086342769999646, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.Payload]": 2.3775477679999995, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[$.no.such.path]": 2.3303828389999808, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_lambda.py::TestTaskServiceLambda::test_raise_exception_catch_output_path[None]": 2.4005710200000294, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sfn.py::TestTaskServiceSfn::test_start_execution_no_such_arn": 1.0211799200000087, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_empty_body": 0.0018898850000823586, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue": 1.9491165770000407, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_send_message_no_such_queue_no_catch": 1.0178791360000332, + "tests/aws/services/stepfunctions/v2/error_handling/test_task_service_sqs.py::TestTaskServiceSqs::test_sqs_failure_in_wait_for_task_tok": 2.662292739999998, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE]": 1.0768799799999442, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_FALSE]": 1.0534181579998858, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE]": 1.061007944000039, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_jsonata_regular_expressions[BASE_SINGLE_QUOTE_FALSE]": 1.069542836000096, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS]": 1.1078546109998797, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[ITEMS_DOUBLE_QUOTES]": 1.0999261280001065, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[MAX_CONCURRENCY]": 1.0597194739999622, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_COUNT]": 1.0752208780000956, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map[TOLERATED_FAILURE_PERCENTAGE]": 1.0725595430000112, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[ITEMS]": 2.2176776669999754, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[MAX_CONCURRENCY]": 2.1793547980000767, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_COUNT]": 3.1054563260000805, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_map_from_input[TOLERATED_FAILURE_PERCENTAGE]": 2.200730883999995, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[HEARTBEAT_SECONDS]": 2.5266225709999617, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task[TIMEOUT_SECONDS]": 0.002711105999992469, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[HEARTBEAT_SECONDS]": 2.4800420120000126, + "tests/aws/services/stepfunctions/v2/evaluate_jsonata/test_base_evaluate_expressions.py::TestBaseEvaluateJsonata::test_base_task_from_input[TIMEOUT_SECONDS]": 0.0017863219999298963, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_PASS_RESULT]": 1.28949098399994, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_base[BASE_RAISE_FAILURE]": 1.2383936980000954, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_catch": 3.012602310000034, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_query_runtime_memory": 2.1333321049999086, + "tests/aws/services/stepfunctions/v2/express/test_express_async.py::TestExpressAsync::test_retry": 10.151664902999869, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_PASS_RESULT]": 0.5914862869999524, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_base[BASE_RAISE_FAILURE]": 0.5341061519999357, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_catch": 2.2793881729999157, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_query_runtime_memory": 1.38322492899988, + "tests/aws/services/stepfunctions/v2/express/test_express_sync.py::TestExpressSync::test_retry": 9.520130922000135, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_0": 0.4742079819998253, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_2": 2.8891771200003404, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_contains": 4.027748927000175, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_get_item": 0.6588288509999529, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_length": 0.6187956930000382, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_partition": 8.245758555999828, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_range": 1.5648690850000548, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py::TestArray::test_array_unique": 0.6667831590000333, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_partition": 6.207614986999943, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py::TestArrayJSONata::test_array_range": 1.0662567840001884, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_decode": 0.9860883209998974, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_encode_decode.py::TestEncodeDecode::test_base_64_encode": 1.0013058220001767, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_context_json_path": 0.6877484039998762, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_escape_sequence": 0.4295273239999915, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_1": 2.500778174000061, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_format_2": 2.7587862509997194, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_1": 0.6432616539998435, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py::TestGeneric::test_nested_calls_2": 0.6700038330000098, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_hash_calculations.py::TestHashCalculations::test_hash": 1.90485677699985, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge": 0.6824515940002129, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_merge_escaped_argument": 0.7305038709998826, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_json_to_string": 2.7572182579999662, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py::TestJsonManipulation::test_string_to_json": 3.4012383069998577, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation_jsonata.py::TestJsonManipulationJSONata::test_parse": 2.288771070000166, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_add": 6.869826987000124, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random": 1.3438252849998662, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py::TestMathOperations::test_math_random_seeded": 0.7354936589997578, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations_jsonata.py::TestMathOperationsJSONata::test_math_random_seeded": 0.0022677639999528765, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split": 2.4778885379998883, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_string_operations.py::TestStringOperations::test_string_split_context_object": 0.6401697809999405, + "tests/aws/services/stepfunctions/v2/intrinsic_functions/test_unique_id_generation.py::TestUniqueIdGeneration::test_uuid": 0.45769851999966704, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_False]": 1.9196748940000816, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[pass_result.json5_ALL_True]": 0.9908747679999124, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_False]": 0.9796712239999579, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[raise_failure.json5_ALL_True]": 0.9870986939997692, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_False]": 1.0200142789999518, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_base[wait_seconds_path.json5_ALL_True]": 0.9978642279997985, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_deleted_log_group": 1.9428755880001063, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_log_group_with_multiple_runs": 1.566590536999911, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_False]": 0.7269797379999545, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_ERROR_True]": 0.7099083269999937, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_False]": 0.9292558710001231, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_FATAL_True]": 0.7180318559996977, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_False]": 0.7226756809998278, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[pass_result.json5_OFF_True]": 0.7028388410001298, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_False]": 0.9936979210001482, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_ERROR_True]": 0.9975493430001734, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_False]": 0.7952365740002278, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_FATAL_True]": 0.7996976970002834, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_False]": 0.7149832509999214, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[raise_failure.json5_OFF_True]": 0.7492653119998067, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_False]": 0.9959574620002059, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_ERROR_True]": 1.006955134000009, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_False]": 0.998658208000279, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_FATAL_True]": 1.0061437690001185, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_False]": 0.9297698969999146, + "tests/aws/services/stepfunctions/v2/logs/test_logs.py::TestLogs::test_partial_log_levels[wait_seconds_path.json5_OFF_True]": 0.733128982999915, + "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_happy_path": 0.4264225490001081, + "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_hybrid_path": 0.37934281299999384, + "tests/aws/services/stepfunctions/v2/mocking/test_aws_scenarios.py::TestBaseScenarios::test_lambda_sqs_integration_retry_path": 7.24169599899983, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC2]": 1.697892518999879, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sfn_start_execution_sync[SFN_SYNC]": 1.6370230309998988, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token": 1.5511406450002596, + "tests/aws/services/stepfunctions/v2/mocking/test_base_callbacks.py::TestBaseScenarios::test_sqs_wait_for_task_token_task_failure": 1.6851659230001133, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_dynamodb_put_get_item": 0.9932859749999352, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_events_put_events": 0.9449624010001116, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke": 0.906180754000161, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_retries": 3.3319053569996413, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke": 0.9719809279999936, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_sync_execution": 0.7941501900002095, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_map_state_lambda": 1.4926458090001233, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_lambda": 1.2328561529998296, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sns_publish_base": 0.9404971290000503, + "tests/aws/services/stepfunctions/v2/mocking/test_base_scenarios.py::TestBaseScenarios::test_sqs_send_message": 0.9938873610001338, + "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_set": 0.005012633000205824, + "tests/aws/services/stepfunctions/v2/mocking/test_mock_config_file.py::TestMockConfigFile::test_is_mock_config_flag_detected_unset": 0.007276159999946685, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_DIRECT_EXPR]": 0.9392965930001083, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EMPTY]": 1.665619930000048, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_EXPR]": 0.9908685890000015, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_cases[BASE_LITERALS]": 0.8452103730000999, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_lambda[BASE_LAMBDA]": 2.6460783670002, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[BOOL]": 0.6946511460002966, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[FLOAT]": 0.6860519970000496, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[INT]": 0.6812071649997051, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[JSONATA_EXPR]": 0.6889342409999699, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_EMPY]": 0.8935893739999301, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[LIST_RICH]": 1.841583842000091, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[NULL]": 0.8879689020000114, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_output_any_non_dict[STR_LIT]": 0.9092745670000113, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_base_task_lambda[BASE_TASK_LAMBDA]": 2.316618697999729, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_FALSE]": 0.7081011359998683, + "tests/aws/services/stepfunctions/v2/outputdecl/test_output.py::TestArgumentsBase::test_output_in_choice[CONDITION_TRUE]": 0.9285104840000713, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSONATA]": 0.4447402749999583, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_base_query_language_field[JSON_PATH]": 0.44798947299977954, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_jsonata_query_language_field_downgrade_exception": 0.001768617999914568, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE]": 0.6457039489998806, + "tests/aws/services/stepfunctions/v2/query_language/test_base_query_language.py::TestBaseQueryLanguage::test_query_language_field_override[JSONATA_OVERRIDE_DEFAULT]": 0.4425207359997785, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONATA_TO_JSONPATH]": 2.270866090999789, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_LEGACY_RESOURCE_JSONPATH_TO_JSONATA]": 2.256475261000105, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONATA_TO_JSONPATH]": 2.247204975999921, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_lambda_task_resource_data_flow[TASK_LAMBDA_SDK_RESOURCE_JSONPATH_TO_JSONATA]": 2.2704015790000085, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONATA_OUTPUT_TO_JSONPATH]": 0.8327309640001204, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_output_to_state[JSONPATH_OUTPUT_TO_JSONATA]": 0.6408809949998613, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_task_dataflow_to_state": 2.3213383639999847, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONATA_ASSIGN_JSONPATH_REF]": 0.8449823830003425, + "tests/aws/services/stepfunctions/v2/query_language/test_mixed_query_language.py::TestMixedQueryLanguageFlow::test_variable_sampling[JSONPATH_ASSIGN_JSONATA_REF]": 0.8544751250001354, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_empty": 2.0946496190001653, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_catch_states_runtime": 2.229218325000147, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO]": 0.7846122289997766, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_aws_docs_scenario[CHOICE_STATE_AWS_SCENARIO_JSONATA]": 0.7415699819998736, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_condition_constant_jsonata": 0.6980513350001729, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE]": 0.7360449399998288, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_JSONATA]": 0.7227133149999645, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_singleton_composite[CHOICE_STATE_SINGLETON_COMPOSITE_LITERAL_JSONATA]": 0.5699995840000156, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.7211124469999959, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_negative[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.6897913329999028, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS]": 0.8966410440000345, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_choice_unsorted_parameters_positive[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.7868698590002623, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_ASSIGN]": 0.7148452590001853, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONATA_COMPARISON_OUTPUT]": 1.773975941999879, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_JSONPATH]": 0.7146583230000942, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_escape_sequence_parsing[ESCAPE_SEQUENCES_STRING_LITERALS]": 0.7909161340000992, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_cause_jsonata": 0.6677077790000112, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_fail_error_jsonata": 0.5111629900000025, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION]": 0.0019808759998340975, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_illegal_escapes[ESCAPE_SEQUENCES_ILLEGAL_INTRINSIC_FUNCTION_2]": 0.0016992469998058368, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_ERRORPATH]": 0.6891218179998759, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_CONTEXTPATH]": 0.7005584889998318, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[INVALID_JSONPATH_IN_STRING_EXPR_JSONPATH]": 0.6902227639998273, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_CAUSEPATH]": 0.7114207609999994, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_HEARTBEATSECONDSPATH]": 0.001539327000045887, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_INPUTPATH]": 0.6957666920000065, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_OUTPUTPATH]": 0.668986607000079, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_invalid_jsonpath[ST.INVALID_JSONPATH_IN_TIMEOUTSECONDSPATH]": 0.001862775000063266, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_empty_retry": 2.1943783780000103, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_base": 9.708754075999877, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_invoke_with_retry_extended_input": 9.831279962000053, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_lambda_service_invoke_with_retry_extended_input": 11.166202810999948, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_batching_base_json_max_per_batch_jsonata": 0.002186030000075334, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_decl": 1.1380153050001809, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_csv_headers_first_line": 1.0332285479998973, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json": 0.8931471930000043, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items": 0.8905870609999056, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_max_items_jsonata": 0.9650359569998272, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[INVALID_ITEMS_PATH]": 1.2413160240000707, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_ITEM_READER]": 1.217259114999706, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_json_with_items_path[VALID_ITEMS_PATH_FROM_PREVIOUS]": 1.2665314050000234, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_base_list_objects_v2": 0.811264969999911, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_first_row_extra_fields": 0.9030878240002949, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_duplicate_headers": 0.8773888149999038, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_decl_extra_fields": 0.8960138250001819, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_headers_first_row_typed_headers": 0.8743885220001175, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[0]": 0.8553814140000213, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[100000000]": 1.7691780130001007, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items[2]": 0.7794854029998532, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[-1]": 0.9095774509999046, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[0]": 1.1133891140000287, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[1.5]": 0.031345150000106514, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000000]": 1.11567041100011, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[100000001]": 1.1778445500001453, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_csv_max_items_paths[2]": 0.906325458000083, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_item_reader_json_no_json_list_object": 0.8365203189998738, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state": 0.8112437160002628, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition": 0.8545210090001092, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_break_condition_legacy": 0.8586147739999888, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch": 0.7737005210001371, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_empty_fail": 0.7955983599999854, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_catch_legacy": 0.7518333069999699, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector": 0.8344266289998359, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_item_selector_parameters": 1.0855700699999034, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_items_path_from_previous": 0.9038763419998759, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_parameters": 0.8352621559995441, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant": 1.6577661879998686, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_distributed_reentrant_lambda": 2.9476741720002337, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_item_selector": 0.8284547560001556, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_config_inline_parameters": 0.8641809170001125, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR]": 1.9208202669997263, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector[MAP_STATE_ITEM_SELECTOR_JSONATA]": 1.7218412080001144, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_parameters": 1.0199989739999182, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_item_selector_singleton": 1.3517550680001023, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[empty]": 0.7060853289999613, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[mixed]": 0.7043382770000335, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata[singleton]": 0.6838700100001915, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[boolean]": 0.7400722889999543, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[function]": 0.001845743000103539, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[null]": 0.7466033939999761, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[number]": 0.7728682519998529, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[object]": 0.7551525199999105, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_fail[string]": 0.7401387880001948, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[boolean]": 0.7687526409999919, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[null]": 0.7676460040001984, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[number]": 0.7963360670000839, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[object]": 0.7575649660002455, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_eval_jsonata_variable_sampling_fail[string]": 0.7636175349998666, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[empty]": 0.7283115559996531, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[mixed]": 0.7238086510001267, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_array[singleton]": 0.6980305829997633, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[boolean]": 0.9194860889999745, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[null]": 0.937086600999919, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[number]": 0.9368682529998296, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[object]": 0.9234919749997061, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_input_types[string]": 0.9231032479997339, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[boolean]": 0.7741270110002461, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[null]": 0.7750261030000729, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[number]": 0.7663786620000792, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[object]": 0.7782698440000786, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_items_variable_sampling[string]": 0.777444499000012, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_label": 0.7020695650001016, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy": 0.8235633350002445, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed": 0.8123498729996754, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_item_selector": 0.8250744770002711, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_distributed_parameters": 0.8134306879999258, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline": 0.8021635969996623, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_item_selector": 0.8113152020000598, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_config_inline_parameters": 0.8325335809997796, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_legacy_reentrant": 1.7160853540001426, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested": 0.8855395880000287, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed": 0.8524062449998837, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_nested_config_distributed_no_max_max_concurrency": 11.418159593999917, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_no_processor_config": 0.7426362169999265, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_legacy": 2.8008766130001277, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_parameters_singleton_legacy": 1.349867192000147, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_result_writer": 1.120530508999991, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry": 3.7055599420000362, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_legacy": 3.6720838690002893, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_retry_multiple_retriers": 7.709946337000019, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[-1]": 0.7066300750002483, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[0]": 0.6854405650001354, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[1]": 0.6788308429997869, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[NoNumber]": 0.6792745289999402, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_count_path[tolerated_failure_count_value0]": 0.7229384269999173, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1.1]": 0.7045981090000168, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[-1]": 0.7021561509998264, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[0]": 0.6857013359999655, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1.1]": 0.7214298229998803, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100.1]": 0.7204644279997865, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[100]": 0.7060034189998987, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[1]": 0.7076801440000509, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[NoNumber]": 0.7041136179998375, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_percentage_path[tolerated_failure_percentage_value0]": 0.7194988119999834, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[count_literal]": 0.7452027930000895, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_map_state_tolerated_failure_values[percentage_literal]": 0.6994805279998673, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[0]": 0.7061886770002275, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[1]": 0.7086216029999832, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[NoNumber]": 0.6702454819999275, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path[max_concurrency_value0]": 0.7172884759997942, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_max_concurrency_path_negative": 0.7846647680000842, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE]": 0.8059208739998667, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state[PARALLEL_STATE_PARAMETERS]": 0.7519475480000892, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_catch": 0.7191368259998399, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_fail": 0.6754760549999901, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_nested": 1.9920146990000376, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_order": 0.8213087239996639, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_parallel_state_retry": 3.61643529700018, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features": 9.525688745000025, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_jitter_none": 4.481658220000099, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_retry_interval_features_max_attempts_zero": 2.413108136000119, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_seconds_jsonata": 0.48202122499969846, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[NANOSECONDS]": 0.5615090470000723, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp[SECONDS]": 0.5522908319996986, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_DATE]": 0.44177298699992207, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_ISO]": 0.4428542570001355, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[INVALID_TIME]": 0.4447496999998748, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[JSONATA]": 0.49902145299984113, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_T]": 0.46884737399977894, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_invalid[NO_Z]": 0.4430368470000303, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_DATE]": 0.0016151310001077945, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_ISO]": 0.0015628520000063872, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[INVALID_TIME]": 0.0016219930000715976, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NANOSECONDS]": 0.685957505000033, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_T]": 0.002044475999809947, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[NO_Z]": 0.0016210199999022734, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_jsonata[SECONDS]": 0.553538036999953, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_DATE]": 0.7780998130001535, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_ISO]": 0.7683033360001446, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[INVALID_TIME]": 0.7929194749999624, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NANOSECONDS]": 0.58559866399969, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_T]": 0.7754160550000506, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[NO_Z]": 0.7684840980002718, + "tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py::TestBaseScenarios::test_wait_timestamp_path[SECONDS]": 0.7819966589997875, + "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_path_based_on_data": 6.43612256599954, + "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_step_functions_calling_api_gateway": 11.396446393999895, + "tests/aws/services/stepfunctions/v2/scenarios/test_sfn_scenarios.py::TestFundamental::test_wait_for_callback": 17.621874498999887, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_base": 4.118166499000381, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_error": 2.7156013860001167, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[HelloWorld]": 3.0321611509998547, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[None]": 3.067009424999924, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[]": 3.0200706580001224, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_body_post[request_body3]": 3.022692707000033, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header1]": 3.1182444890000625, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[custom_header2]": 3.07097103800038, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_headers[singleStringHeader]": 0.0037154799999825627, + "tests/aws/services/stepfunctions/v2/services/test_apigetway_task_service.py::TestTaskApiGateway::test_invoke_with_query_parameters": 3.2177992010001617, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_delete_item": 0.9897777810001571, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_get_item": 1.2329310979998809, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_dynamodb_put_update_get_item": 1.312746124000114, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_list_secrets": 0.9977916159998585, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[binary]": 1.0595202019999306, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[bytearray]": 1.091995789000066, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_binary]": 1.123359242000106, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[empty_str]": 2.32296782100002, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_get_object[str]": 1.1080312020001202, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[bool]": 1.1920609450000939, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[dict]": 1.1866718729997956, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[list]": 1.146628499000144, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[num]": 1.198170314999743, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_s3_put_object[str]": 1.1979594849999557, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template0]": 0.9330477029998292, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_send_task_outcome_with_no_such_token[state_machine_template1]": 0.9468135019999409, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution": 0.9679647890000069, + "tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py::TestTaskServiceAwsSdk::test_sfn_start_execution_implicit_json_serialisation": 1.0140498009998282, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_DELETE_ITEM]": 1.2516988969998692, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_GET_ITEM]": 1.239201869999988, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_QUERY]": 1.2573893649996535, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_base_integrations[DYNAMODB_PUT_UPDATE_GET_ITEM]": 1.5652200629999697, + "tests/aws/services/stepfunctions/v2/services/test_dynamodb_task_service.py::TestTaskServiceDynamoDB::test_invalid_integration": 0.5752439670000058, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task": 0.0017432199999802833, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_raise_failure": 0.001806740000120044, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync": 0.0016646930000661087, + "tests/aws/services/stepfunctions/v2/services/test_ecs_task_service.py::TestTaskServiceECS::test_run_task_sync_raise_failure": 0.0017207180001150846, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_base": 1.982950616999915, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_malformed_detail": 0.894148906999817, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_mixed_malformed_detail": 0.9478064970001014, + "tests/aws/services/stepfunctions/v2/services/test_events_task_service.py::TestTaskServiceEvents::test_put_events_no_source": 31.854582123000228, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_bytes_payload": 2.018886423999902, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0.0]": 2.06793529499987, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_0]": 3.2550308579998273, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[0_1]": 2.0483586929997273, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[HelloWorld]": 2.0362787470000967, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[True]": 2.042098295000187, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value5]": 2.053750906000232, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_json_values[json_value6]": 2.0325890910000908, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_pipe": 3.6632819429999017, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_invoke_string_payload": 2.031128168999885, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task.py::TestTaskLambda::test_lambda_task_filter_parameters_input": 2.1706618270000035, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke": 2.5072321759998886, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_bytes_payload": 2.471136002000094, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0.0]": 2.4771756729999197, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_0]": 2.572512377000294, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[0_1]": 2.553214161000369, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[HelloWorld]": 2.5674561520002044, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[True]": 2.4968335459998343, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value5]": 2.5005254980001155, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_json_values[json_value6]": 2.606032099000231, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_invoke_unsupported_param": 2.535141339999882, + "tests/aws/services/stepfunctions/v2/services/test_lambda_task_service.py::TestTaskServiceLambda::test_list_functions": 0.0021452949997637916, + "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution": 1.0617732579999029, + "tests/aws/services/stepfunctions/v2/services/test_sfn_task_service.py::TestTaskServiceSfn::test_start_execution_input_json": 1.061448663000192, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params0-True]": 0.9668997020003189, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_fifo_message_attribute[input_params1-False]": 0.9634459880001032, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[1]": 0.9355714869998337, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[HelloWorld]": 0.9497061929998836, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[None]": 0.9528188350000164, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[True]": 0.89841021500024, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[]": 0.9223674600000322, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base[message1]": 2.1569079570001577, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_base_error_topic_arn": 0.9447511569997005, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[\"HelloWorld\"]": 1.0963551620002363, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[HelloWorld]": 1.0862289520000559, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[message_value3]": 1.0763198269999066, + "tests/aws/services/stepfunctions/v2/services/test_sns_task_service.py::TestTaskServiceSns::test_publish_message_attributes[{}]": 1.2019662269999571, + "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message": 1.1226674090003144, + "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_attributes": 1.1397137309998016, + "tests/aws/services/stepfunctions/v2/services/test_sqs_task_service.py::TestTaskServiceSqs::test_send_message_unsupported_parameters": 1.0902701209997758, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING]": 2.271225279000191, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_catch_error_variable_sampling[TASK_CATCH_ERROR_VARIABLE_SAMPLING_TO_JSONPATH]": 2.286643020999918, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT]": 0.002240304000224569, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.0016859030001796782, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_map_catch_error[MAP_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0018447810000452591, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT]": 0.001648893999572465, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_OUTPUT_WITH_RETRY]": 0.0018452719998549583, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_parallel_catch_error[PARALLEL_CATCH_ERROR_VARIABLE_SAMPLING]": 0.0018631659997936367, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT]": 2.293300575000103, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_output[TASK_CATCH_ERROR_OUTPUT_TO_JSONPATH]": 2.5326370970001335, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY]": 3.5701209399999243, + "tests/aws/services/stepfunctions/v2/states_variables/test_error_output.py::TestStateVariablesTemplate::test_task_catch_error_with_retry[TASK_CATCH_ERROR_OUTPUT_WITH_RETRY_TO_JSONPATH]": 3.627364130999922, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dump]": 1.4766658039995946, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_create_describe[dumps]": 1.4917202420001558, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dump]": 1.477878683000199, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_cloudformation_definition_string_create_describe[dumps]": 1.475112265000007, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_invalid_sm": 0.5670255590000579, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_delete_valid_sm": 1.5661167880002722, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_definition_format_sm": 0.4505287040001349, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_duplicate_sm_name": 1.7511335349997807, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_exact_duplicate_sm": 0.4932173079998847, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition": 0.49921580299997004, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_definition_and_role": 0.6285446400002002, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_role_arn": 0.6043942010001047, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_base_update_none": 0.4994148020000466, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_create_update_state_machine_same_parameters": 0.5461930839999241, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_delete_nonexistent_sm": 0.4390144710000641, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution": 0.7399211969998305, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_arn_containing_punctuation": 0.7449122499997429, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_invalid_arn": 0.4221983480001654, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_execution_no_such_state_machine": 0.49539521800011244, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_invalid_arn_sm": 0.4198591919998762, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_nonexistent_sm": 0.440397768999901, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_sm_arn_containing_punctuation": 0.44319619900011276, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_describe_state_machine_for_execution": 0.514936175999992, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_invalid_arn": 0.44265911100001176, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_no_such_execution": 0.495014928000046, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_get_execution_history_reversed": 0.7330165590001343, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_arn": 0.4360459429999537, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_invalid_start_execution_input": 0.7699120600002516, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_invalid_arn": 0.41587004400003025, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_execution_no_such_state_machine": 0.4322643390000849, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_pagination": 1.550744270999985, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_executions_versions_pagination": 1.8406724149999718, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms": 0.5634256199998617, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_list_sms_pagination": 0.9483249570002954, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution": 0.6017307099996287, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_execution_idempotent": 1.1412688030000027, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_start_sync_execution": 0.46342318600022736, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_state_machine_status_filter": 0.8187563890001002, + "tests/aws/services/stepfunctions/v2/test_sfn_api.py::TestSnfApi::test_stop_execution": 0.5165142480000213, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[\\x00activity]": 0.3368011079999178, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity name]": 0.3380298750003021, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\"name]": 0.33945187600011195, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity#name]": 0.34405955399984123, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity$name]": 0.3375462469998638, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity%name]": 0.34673121600008017, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity&name]": 0.33970683699999427, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity*name]": 0.3352185940000254, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity,name]": 0.34121634899997844, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity/name]": 0.33955077499990693, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity:name]": 0.33318260400005784, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity;name]": 0.33621541899992735, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activityname]": 0.3340964039998653, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity?name]": 0.33528881899974294, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity[name]": 0.3349531090000255, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\\\name]": 0.3492175080000379, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x1f]": 0.34239502000036737, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity\\x7f]": 0.3408857099998386, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity]name]": 0.3344990090001829, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity^name]": 0.33416171599992595, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity`name]": 0.33772114300018075, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity{name]": 0.3386401970001316, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity|name]": 0.33290669400003026, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity}name]": 0.33765228200013553, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_activity_invalid_name[activity~name]": 0.33282544899998356, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[ACTIVITY_NAME_ABC]": 0.4142723490001572, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[Activity1]": 0.42317133299980014, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]": 0.41143609599976116, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name.1]": 0.4101710020001974, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity-name_123]": 0.41722159000005377, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name.v2]": 0.4174094940001396, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity.name]": 0.413752784000053, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activityName.with.dots]": 0.4185765260001517, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_create_describe_delete_activity[activity_123.name]": 0.4089378559999659, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_activity_invalid_arn": 0.4209794549997241, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_describe_deleted_activity": 0.357016509999994, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_deleted": 0.3550243050001427, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_get_activity_task_invalid_arn": 0.4175937470001827, + "tests/aws/services/stepfunctions/v2/test_sfn_api_activities.py::TestSnfApiActivities::test_list_activities": 0.36548274500000844, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_create_alias_single_router_config": 0.6928044819997012, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_delete_list": 0.8603175329999431, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_invoke_describe_list": 0.857750512000166, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_base_lifecycle_create_update_describe": 0.7422816150001381, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_no_such_alias_arn": 0.7106519449998814, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_revision_with_alias": 0.6999896949998856, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_delete_version_with_alias": 0.7388269680000121, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_name": 0.7417117459999645, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_invalid_router_configs": 0.7631969019998905, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_not_idempotent": 0.717078107000134, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_error_create_alias_with_state_machine_arn": 0.6760567549999905, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_idempotent_create_alias": 0.725955075000229, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_invalid_next_token": 2.0208766719999858, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[0]": 0.7857605190001777, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_list_state_machine_aliases_pagination_max_results[1]": 0.7947279500003788, + "tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py::TestSfnApiAliasing::test_update_no_such_alias_arn": 0.7166258560002916, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_create_describe_delete": 0.7716222649999054, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_activity_task": 0.9040272729998833, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[SYNC]": 0.8857305789999828, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_illegal_callbacks[WAIT_FOR_TASK_TOKEN]": 0.8932121579998693, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_async_describe_history_execution": 1.4090149640001073, + "tests/aws/services/stepfunctions/v2/test_sfn_api_express.py::TestSfnApiExpress::test_start_sync_execution": 0.7849841120000747, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_deleted_log_group": 0.5163986180002667, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration0]": 0.44727006800007985, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_incomplete_logging_configuration[logging_configuration1]": 0.44390212500002235, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration0]": 0.39759805199992115, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration1]": 0.41346277500019823, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_invalid_logging_configuration[logging_configuration2]": 0.4264885919999415, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-False]": 0.4703576150002391, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ALL-True]": 0.4728800569998839, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-False]": 0.4786683389997961, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[ERROR-True]": 0.47258406299988565, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-False]": 0.4792527639999662, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[FATAL-True]": 0.4770764439999766, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-False]": 0.4735895460003121, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_logging_configuration[OFF-True]": 0.4723136439999962, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_multiple_destinations": 0.45495803999983764, + "tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py::TestSnfApiLogs::test_update_logging_configuration": 0.6019411689999288, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_list_map_runs_and_describe_map_run": 0.8576407320001636, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_empty_fail": 0.35191293800016865, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[ ]": 0.3506056109997644, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\"]": 0.32982690399990133, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[#]": 0.3276954829998431, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[$]": 0.3246295190001547, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[%]": 0.3261837289999221, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[&]": 1.6938628529999278, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[*]": 0.3258931550001307, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[,]": 0.3307864320001954, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[:]": 0.3282021719999193, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[;]": 0.32615697699998236, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[<]": 0.3334736399997382, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[>]": 0.32718663800005743, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[?]": 0.3295107179999377, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[[]": 0.32761836600002425, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\\\]": 0.3250761499998589, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\n]": 0.33547590799980753, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\r]": 0.32938755899999705, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\t]": 0.33218242299994927, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x00]": 0.3284077900000284, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x01]": 0.32993894299988824, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x02]": 0.321459822000179, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x03]": 0.3281756909998421, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x04]": 0.3291063670001222, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x05]": 0.33085728600008224, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x06]": 0.3305005490001349, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x07]": 0.34203229899981125, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x08]": 0.3281821010002659, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0b]": 0.33034188500005257, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0c]": 0.3351678120000088, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0e]": 0.32940865799992025, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x0f]": 0.3298237320000226, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x10]": 0.3286126640000475, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x11]": 0.3277359639998849, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x12]": 0.32925342799990176, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x13]": 0.32937344100014343, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x14]": 0.32895528800008833, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x15]": 0.3282456560000355, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x16]": 0.3318043339997985, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x17]": 0.33085765500004527, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x18]": 0.33264396799995666, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x19]": 0.3309974209998927, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1a]": 0.3363972999998168, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1b]": 0.32649115499998516, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1c]": 0.33742263899989666, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1d]": 0.3773706740000762, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1e]": 0.3299703960001352, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x1f]": 0.32758331500008353, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x7f]": 0.3292098530000658, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x80]": 0.3277058229996328, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x81]": 0.33242842399977235, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x82]": 0.32792199600021377, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x83]": 0.3318673490000492, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x84]": 0.3282988429998568, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x85]": 0.33017332199983684, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x86]": 0.3273081680001724, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x87]": 0.3305009409998547, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x88]": 0.3256639100002303, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x89]": 0.3314512330000525, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8a]": 0.32777993199988487, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8b]": 0.33442435300003126, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8c]": 0.33064423300015733, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8d]": 0.3304201340001782, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8e]": 0.32759585299982064, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x8f]": 0.3299169259998962, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x90]": 0.32983817999956955, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x91]": 0.33439979700006006, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x92]": 0.3290636890001224, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x93]": 0.3326360679998288, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x94]": 0.3291582550000385, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x95]": 0.32896573399989393, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x96]": 0.33239167200008524, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x97]": 0.33674936599982175, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x98]": 0.32700045599995065, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x99]": 0.3325904239998181, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9a]": 0.37091630099985196, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9b]": 0.37650492399984614, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9c]": 0.3384207690000949, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9d]": 1.6259138780001194, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9e]": 0.33246067400000356, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[\\x9f]": 0.3272925710000436, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[]]": 0.32423073600011776, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[^]": 0.3248606050001399, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[`]": 0.34113608500001646, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[{]": 0.3298722170002293, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[|]": 0.3265133329998662, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[}]": 0.3313125869999567, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_invalid_char_fail[~]": 0.32885359500005507, + "tests/aws/services/stepfunctions/v2/test_sfn_api_map_run.py::TestSnfApiMapRun::test_map_state_label_too_long_fail": 0.3400744279999799, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_create_state_machine": 0.3571099470000263, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[None]": 0.34152759299990976, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list1]": 0.34669245999998566, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list2]": 0.3423924779999652, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_invalid_state_machine[tag_list3]": 0.35245546300006936, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list0]": 0.3664681379996182, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list1]": 0.3590634939996562, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list2]": 0.3614021739997497, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list3]": 0.35864775400000326, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine[tag_list4]": 0.3578839950000656, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_tag_state_machine_version": 0.3583746679998967, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys0]": 0.3652974269996321, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys1]": 0.3755231500001628, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys2]": 0.3696259509999891, + "tests/aws/services/stepfunctions/v2/test_sfn_api_tagging.py::TestSnfApiTagging::test_untag_state_machine[tag_keys3]": 0.3694528280000213, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_DICT]": 0.3363730870000836, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[EMPTY_STRING]": 0.3477899899999102, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_not_a_definition[NOT_A_DEF]": 0.34660216199995375, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[ILLEGAL_WFTT]": 0.35568255299995144, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[INVALID_BASE_NO_STARTAT]": 0.3396652270000686, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_express[VALID_BASE_PASS]": 0.34117634399990493, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[INVALID_BASE_NO_STARTAT]": 0.3832413459997497, + "tests/aws/services/stepfunctions/v2/test_sfn_api_validation.py::TestSfnApiValidation::test_validate_state_machine_definition_type_standard[VALID_BASE_PASS]": 0.35286060099997485, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_INTRINSIC_FUNCTION]": 1.9856783699999596, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_PARAMETERS]": 0.8882004340000549, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_ASSIGN_FROM_RESULT]": 0.9637832120001804, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_EVALUATION_ORDER_PASS_STATE]": 0.942784008000217, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_CHOICE]": 0.9298744799998531, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_FAIL]": 0.8719726659999196, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INPUTPATH]": 0.8622858539999925, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.1803678589999436, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_ITERATOR_OUTER_SCOPE]": 1.5339152630001536, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_OUTPUTPATH]": 0.9018367460002992, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_PARAMETERS]": 0.8949603029998343, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[BASE_REFERENCE_IN_WAIT]": 0.8730864280000787, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_INTRINSIC_FUNCTION]": 1.2214383420002832, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEMS_PATH]": 1.2074574170001142, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_ITEM_SELECTOR]": 1.1559692450002785, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_CONCURRENCY_PATH]": 0.8961626989998877, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_MAX_ITEMS_PATH]": 0.9706274860000121, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_assign_templates[MAP_STATE_REFERENCE_IN_TOLERATED_FAILURE_PATH]": 2.175228058999892, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_CONDITION_CONSTANT_JSONATA]": 0.5322969609999291, + "tests/aws/services/stepfunctions/v2/test_sfn_api_variable_references.py::TestSfnApiVariableReferences::test_base_variable_references_in_jsonata_template[CHOICE_STATE_UNSORTED_CHOICE_PARAMETERS_JSONATA]": 0.5422913230001996, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_express_with_publish": 0.41108208100013144, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_no_version_description": 0.45711884100001043, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_publish_describe_with_version_description": 0.4538222099997711, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_publish": 0.427361640999834, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_create_with_version_description_no_publish": 0.3979294299997491, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version": 0.5249433779997617, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_describe_state_machine_for_execution_of_version_with_revision": 0.5927497609998227, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_no_publish_on_creation": 0.4524019919999773, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_empty_revision_with_publish_and_publish_on_creation": 0.4503545479999502, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_idempotent_publish": 0.46116135400006897, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_delete_version": 0.48927533400001266, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_list_state_machine_versions_pagination": 0.885232938000172, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version": 0.5600497539999196, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_invalid_arn": 0.4231483779997234, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_publish_state_machine_version_no_such_machine": 0.4293700870000521, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_start_version_execution": 0.5884895859999233, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_update_state_machine": 0.49327430300013475, + "tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py::TestSnfApiVersioning::test_version_ids_between_deletions": 0.473452239000153, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_CHOICE_STATE]": 0.9913197329999548, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_FAIL_STATE]": 0.809541893000187, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_PASS_STATE]": 0.8136600550001276, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_RESULT_PASS_STATE]": 0.8269062280003254, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[BASE_SUCCEED_STATE]": 0.8046169049998753, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_PASS_STATE]": 0.9167654799998672, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_debug[IO_RESULT_PASS_STATE]": 0.9101049669998247, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_CHOICE_STATE]": 0.6792781960000411, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_FAIL_STATE]": 0.49446252400025514, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_PASS_STATE]": 0.517675921000091, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_RESULT_PASS_STATE]": 0.5020276070001728, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[BASE_SUCCEED_STATE]": 0.4844849159997011, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_PASS_STATE]": 0.5938921979998213, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_info[IO_RESULT_PASS_STATE]": 0.6026822859998902, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_CHOICE_STATE]": 0.9765238819998103, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_FAIL_STATE]": 0.7969647749998785, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_PASS_STATE]": 2.1475326719998975, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_RESULT_PASS_STATE]": 0.8144717749999018, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[BASE_SUCCEED_STATE]": 0.7937724099999741, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_PASS_STATE]": 0.9058040720001372, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_inspection_level_trace[IO_RESULT_PASS_STATE]": 0.9052586999998766, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[DEBUG]": 2.4057382719997804, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[INFO]": 2.4370863329997974, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_service_task_state[TRACE]": 2.430307793000111, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[DEBUG]": 2.390642860999833, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[INFO]": 2.3671705559997918, + "tests/aws/services/stepfunctions/v2/test_state/test_test_state_scenarios.py::TestStateCaseScenarios::test_base_lambda_task_state[TRACE]": 2.38567082700024, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_choice_state_machine": 2.765084474999867, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_map_state_machine": 1.162906337000095, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_run_state_machine": 1.5634788800000479, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_create_state_machines_in_parallel": 1.8815592899998137, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_events_state_machine": 0.0019113250000373228, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_intrinsic_functions": 1.2400464820000252, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::TestStateMachine::test_try_catch_state_machine": 10.155057945999943, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_aws_sdk_task": 1.2519743830000607, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_default_logging_configuration": 0.06880921900028625, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-central-1]": 0.0017563150001933536, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-eu-west-1]": 0.001647911999725693, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-1]": 0.0022215280000636994, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_multiregion_nested[statemachine_definition0-us-east-2]": 0.001648332000058872, + "tests/aws/services/stepfunctions/v2/test_stepfunctions_v2.py::test_run_aws_sdk_secrets_manager": 3.3910519029996067, + "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_no_timeout": 6.014973788000134, + "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_path_timeout": 6.040687306999871, + "tests/aws/services/stepfunctions/v2/timeouts/test_heartbeats.py::TestHeartbeats::test_heartbeat_timeout": 6.06758956800013, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_lambda": 6.8092528540000785, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda": 6.79333181800007, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_fixed_timeout_service_lambda_with_path": 8.372074332000011, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_global_timeout": 5.764223941999944, + "tests/aws/services/stepfunctions/v2/timeouts/test_timeouts.py::TestTimeouts::test_service_lambda_map_timeout": 0.006783327999755784, + "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_assume_role_tag_validation": 0.16735422499982633, + "tests/aws/services/sts/test_sts.py::TestSTSAssumeRoleTagging::test_iam_role_chaining_override_transitive_tags": 0.2277820150000025, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_non_existent_role": 0.017126989999951547, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role": 0.24108989900014421, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_saml": 0.06416091200003393, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_assume_role_with_web_identity": 0.05119679199992788, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_expiration_date_format": 0.01964581399988674, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[False]": 0.09611626899982184, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_role_access_key[True]": 0.10326076999990619, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_root": 0.014585191000151099, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[False]": 0.07541335100017932, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_caller_identity_user_access_key[True]": 0.2253269340001225, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_get_federation_token": 0.13733134400013114, + "tests/aws/services/sts/test_sts.py::TestSTSIntegrations::test_sts_invalid_parameters": 0.06422330800023701, + "tests/aws/services/support/test_support.py::TestConfigService::test_support_case_lifecycle": 0.07045699299987973, + "tests/aws/services/swf/test_swf.py::TestSwf::test_run_workflow": 0.19254779799985045, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_deletion": 0.22399721199963096, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_failing_start_transcription_job": 0.382694526999785, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_get_transcription_job": 2.5941830710000886, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_list_transcription_jobs": 2.5844109000001936, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_invalid_length": 32.10154500700014, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_error_speaker_labels": 0.0016038480000588606, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_happy_path": 3.7003231660003166, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_speaker_diarization": 0.0017655609999565058, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[None-None]": 2.3697087269999884, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-2-None]": 5.228627965999749, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-3-test-output]": 3.081919844999902, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-4-test-output.json]": 2.429251186999636, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-5-test-files/test-output.json]": 5.044386555999836, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job[test-output-bucket-6-test-files/test-output]": 3.085109133999822, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_start_job_same_name": 4.894791574999999, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.amr-hello my name is]": 2.169999917000041, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.flac-hello my name is]": 2.1667974499998763, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp3-hello my name is]": 2.1583806099999947, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.mp4-hello my name is]": 2.1560373460001756, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.ogg-hello my name is]": 2.1774369119998482, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-gb.webm-hello my name is]": 2.6983466999997745, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mkv-one of the most vital]": 2.17030563000003, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_supported_media_formats[../../files/en-us_video.mp4-one of the most vital]": 2.151646561000007, + "tests/aws/services/transcribe/test_transcribe.py::TestTranscribe::test_transcribe_unsupported_media_format_failure": 3.2049751639999613, + "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_error_injection": 25.74096652000003, + "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_read_error_injection": 25.7232662720005, + "tests/aws/test_error_injection.py::TestErrorInjection::test_dynamodb_write_error_injection": 51.3588747660001, + "tests/aws/test_error_injection.py::TestErrorInjection::test_kinesis_error_injection": 2.10488346499983, + "tests/aws/test_integration.py::TestIntegration::test_firehose_extended_s3": 0.20324049999953786, + "tests/aws/test_integration.py::TestIntegration::test_firehose_kinesis_to_s3": 33.43547338500002, + "tests/aws/test_integration.py::TestIntegration::test_firehose_s3": 0.3588276670002415, + "tests/aws/test_integration.py::TestIntegration::test_lambda_streams_batch_and_transactions": 40.823294548000376, + "tests/aws/test_integration.py::TestIntegration::test_scheduled_lambda": 26.26788192999993, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.10]": 1.8959259440002825, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.11]": 1.8815405780001129, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.12]": 1.888854125999842, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.13]": 1.9315528890006135, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.8]": 1.9207606690006287, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_put_item_to_dynamodb[python3.9]": 1.908511814999656, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.10]": 7.814196494000498, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.11]": 7.824846277999768, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.12]": 1.8040210570002273, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.13]": 7.823655225000039, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.8]": 15.924464540999452, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_send_message_to_sqs[python3.9]": 1.8366358099997342, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.10]": 3.9846717099999296, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.11]": 3.9398167390004346, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.12]": 3.9829633240001385, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.13]": 3.923600472000089, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.8]": 4.014908554000158, + "tests/aws/test_integration.py::TestLambdaOutgoingSdkCalls::test_lambda_start_stepfunctions_execution[python3.9]": 4.028950583000096, + "tests/aws/test_integration.py::test_kinesis_lambda_forward_chain": 0.0028555869994306704, + "tests/aws/test_moto.py::test_call_include_response_metadata": 0.008041624999805208, + "tests/aws/test_moto.py::test_call_multi_region_backends": 0.018307062999610935, + "tests/aws/test_moto.py::test_call_non_implemented_operation": 0.0473054720005166, + "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[IO[bytes]]": 0.025920976000179508, + "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[bytes]": 0.02412087899938342, + "tests/aws/test_moto.py::test_call_s3_with_streaming_trait[str]": 0.0542775700000675, + "tests/aws/test_moto.py::test_call_sqs_invalid_call_raises_http_exception": 0.008548395999696368, + "tests/aws/test_moto.py::test_call_with_es_creates_state_correctly": 0.07448774299973593, + "tests/aws/test_moto.py::test_call_with_modified_request": 0.011340222999933758, + "tests/aws/test_moto.py::test_call_with_sns_with_full_uri": 0.005789970999558136, + "tests/aws/test_moto.py::test_call_with_sqs_creates_state_correctly": 4.0254183940000985, + "tests/aws/test_moto.py::test_call_with_sqs_invalid_call_raises_exception": 0.00795487300001696, + "tests/aws/test_moto.py::test_call_with_sqs_modifies_state_in_moto_backend": 0.010001802999795473, + "tests/aws/test_moto.py::test_call_with_sqs_returns_service_response": 0.007783062000271457, + "tests/aws/test_moto.py::test_moto_fallback_dispatcher": 0.015714498999386706, + "tests/aws/test_moto.py::test_moto_fallback_dispatcher_error_handling": 0.03709013900015634, + "tests/aws/test_moto.py::test_request_with_response_header_location_fields": 0.11823217599976488, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_localstack_backends": 0.24572198199984996, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_account_id_namespacing_for_moto_backends": 1.9283294909996584, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_dynamodb": 0.3635614220002026, + "tests/aws/test_multi_accounts.py::TestMultiAccounts::test_multi_accounts_kinesis": 1.4751496619996942, + "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_api_gateway": 0.5254930859996421, + "tests/aws/test_multiregion.py::TestMultiRegion::test_multi_region_sns": 0.08228373600013583, + "tests/aws/test_network_configuration.py::TestLambda::test_function_url": 1.158958812000492, + "tests/aws/test_network_configuration.py::TestLambda::test_http_api_for_function_url": 0.0018379289995209547, + "tests/aws/test_network_configuration.py::TestOpenSearch::test_default_strategy": 10.798005630999342, + "tests/aws/test_network_configuration.py::TestOpenSearch::test_path_strategy": 10.660830504000387, + "tests/aws/test_network_configuration.py::TestOpenSearch::test_port_strategy": 10.514160446000005, + "tests/aws/test_network_configuration.py::TestS3::test_201_response": 0.09132229700026073, + "tests/aws/test_network_configuration.py::TestS3::test_multipart_upload": 0.11469660500051759, + "tests/aws/test_network_configuration.py::TestS3::test_non_us_east_1_location": 0.07277084000043033, + "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[domain]": 0.02658701500013194, + "tests/aws/test_network_configuration.py::TestSQS::test_domain_based_strategies[standard]": 0.02236288299991429, + "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_with_external_port": 0.02226754399953279, + "tests/aws/test_network_configuration.py::TestSQS::test_off_strategy_without_external_port": 0.023943748999499803, + "tests/aws/test_network_configuration.py::TestSQS::test_path_strategy": 0.02413144399997691, + "tests/aws/test_notifications.py::TestNotifications::test_sns_to_sqs": 0.1591214480004055, + "tests/aws/test_notifications.py::TestNotifications::test_sqs_queue_names": 0.02143897400037531, + "tests/aws/test_serverless.py::TestServerless::test_apigateway_deployed": 0.0369542140001613, + "tests/aws/test_serverless.py::TestServerless::test_dynamodb_stream_handler_deployed": 0.04413841800032969, + "tests/aws/test_serverless.py::TestServerless::test_event_rules_deployed": 105.67022652899959, + "tests/aws/test_serverless.py::TestServerless::test_kinesis_stream_handler_deployed": 0.0017467190000388655, + "tests/aws/test_serverless.py::TestServerless::test_lambda_with_configs_deployed": 0.020848137000029965, + "tests/aws/test_serverless.py::TestServerless::test_queue_handler_deployed": 0.03848556799994185, + "tests/aws/test_serverless.py::TestServerless::test_s3_bucket_deployed": 26.70419621999963, + "tests/aws/test_terraform.py::TestTerraform::test_acm": 0.0017701019996820833, + "tests/aws/test_terraform.py::TestTerraform::test_apigateway": 0.0017533390000608051, + "tests/aws/test_terraform.py::TestTerraform::test_apigateway_escaped_policy": 0.0017782670001906808, + "tests/aws/test_terraform.py::TestTerraform::test_bucket_exists": 0.005197633000079804, + "tests/aws/test_terraform.py::TestTerraform::test_dynamodb": 0.0018321879997529322, + "tests/aws/test_terraform.py::TestTerraform::test_event_source_mapping": 0.0018192429997725412, + "tests/aws/test_terraform.py::TestTerraform::test_lambda": 0.0018459739999343583, + "tests/aws/test_terraform.py::TestTerraform::test_route53": 0.0017712640001263935, + "tests/aws/test_terraform.py::TestTerraform::test_security_groups": 0.001846765000209416, + "tests/aws/test_terraform.py::TestTerraform::test_sqs": 0.0017431309997846256, + "tests/aws/test_validate.py::TestMissingParameter::test_elasticache": 0.0017749009998624388, + "tests/aws/test_validate.py::TestMissingParameter::test_opensearch": 0.001805597999918973, + "tests/aws/test_validate.py::TestMissingParameter::test_sns": 0.0018174609995185165, + "tests/aws/test_validate.py::TestMissingParameter::test_sqs_create_queue": 0.001736708999487746, + "tests/aws/test_validate.py::TestMissingParameter::test_sqs_send_message": 0.0018227000000479165, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_container_starts_non_root": 0.0019343990002198552, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_custom_docker_flags": 0.0018188630001532147, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_logs": 0.0018221489999632468, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_pulling_image_message": 0.001830905000133498, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_restart": 0.0018113390001417429, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_already_running": 0.0018184319997089915, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_cli_within_container": 0.0019496380000418867, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_start_wait_stop": 0.0018140340002901212, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_status_services": 0.0017478500003562658, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_volume_dir_mounted_correctly": 0.0018114390004484449, + "tests/cli/test_cli.py::TestCliContainerLifecycle::test_wait_timeout_raises_exception": 0.0018399629998384626, + "tests/cli/test_cli.py::TestDNSServer::test_dns_port_not_published_by_default": 0.001772525999967911, + "tests/cli/test_cli.py::TestDNSServer::test_dns_port_published_with_flag": 0.001935911999680684, + "tests/cli/test_cli.py::TestHooks::test_prepare_host_hook_called_with_correct_dirs": 0.5751379260000249, + "tests/cli/test_cli.py::TestImports::test_import_venv": 0.00690229200063186, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_404_unfortunately_detected_as_s3_request": 0.03261071599990828, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_internal_failure_handler_http_errors": 0.016988175999813393, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_http_errors": 0.0018436099999235012, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_get_unexpected_errors": 0.0018149250004171336, + "tests/integration/aws/test_app.py::TestExceptionHandlers::test_router_handler_patch_http_errors": 0.13156539600049655, + "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_http": 0.10604796100005842, + "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https": 0.08910362699998586, + "tests/integration/aws/test_app.py::TestHTTP2Support::test_http2_https_localhost": 0.0718038810005055, + "tests/integration/aws/test_app.py::TestHttps::test_default_cert_works": 0.06438380200006577, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_return_response": 0.0017640109995227249, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_ssl_websockets": 0.0017440920000808546, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websocket_reject_through_edge_router": 0.001719536000109656, + "tests/integration/aws/test_app.py::TestWebSocketIntegration::test_websockets_served_through_edge_router": 0.001865430000179913, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_request_streaming": 0.12501858199993876, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_chunked_response_streaming": 0.13347143900000447, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_raw_header_handling": 0.115769065999757, + "tests/integration/aws/test_app.py::TestWerkzeugIntegration::test_response_close_handlers_called_with_router": 0.11566933100039023, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-False]": 0.001923569000155112, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-False-True]": 0.0019344699999237491, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-False]": 0.0018075210005008557, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[CmdDockerClient-True-True]": 0.0019221070001549379, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-False]": 2.9956908780009144, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-False-True]": 2.993862503999935, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-False]": 3.0049512020000293, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_build_image[SdkDockerClient-True-True]": 2.3431261149999045, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[CmdDockerClient]": 0.0018445810001139762, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_container_lifecycle_commands[SdkDockerClient]": 22.0830238489998, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[CmdDockerClient]": 0.0019575040000745503, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_content_into_container[SdkDockerClient]": 0.29213414900004864, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[CmdDockerClient]": 0.0018308250000700355, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_into_container[SdkDockerClient]": 0.23115510399975392, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[CmdDockerClient]": 0.005962507999811351, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_directory_structure_into_container[SdkDockerClient]": 0.2445928779998212, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[CmdDockerClient]": 0.0018418360000396206, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container[SdkDockerClient]": 0.2572800719999577, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[CmdDockerClient]": 0.001889646000108769, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_into_directory[SdkDockerClient]": 0.2402652990003844, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[CmdDockerClient]": 0.002009339999403892, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_container_to_different_file[SdkDockerClient]": 0.2632229269997879, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[CmdDockerClient]": 0.0019540260000212584, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_from_non_existent_container[SdkDockerClient]": 0.010071746999983588, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[CmdDockerClient]": 0.004692545000125392, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container[SdkDockerClient]": 0.19783348499959175, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[CmdDockerClient]": 0.0019382270002097357, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_with_existing_target[SdkDockerClient]": 0.38041903599969373, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[CmdDockerClient]": 0.001804746999823692, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_container_without_target_filename[SdkDockerClient]": 0.22246420400006173, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[CmdDockerClient]": 0.001845000999765034, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_copy_into_non_existent_container[SdkDockerClient]": 0.008623763000287, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[CmdDockerClient]": 0.0020428029997674457, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_non_existing_image[SdkDockerClient]": 0.9214604010003313, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[CmdDockerClient]": 0.0018250140001327964, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_remove_removes_container[SdkDockerClient]": 1.1923609159994157, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[CmdDockerClient]": 0.0018006079999395297, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_init[SdkDockerClient]": 0.02847178299953157, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[CmdDockerClient]": 0.0018297319998055173, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_container_with_max_env_vars[SdkDockerClient]": 0.23109793699950387, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[CmdDockerClient]": 0.001955118000296352, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_file_in_container[SdkDockerClient]": 0.20713527699990664, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-False]": 0.0018027219998657529, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[CmdDockerClient-True]": 0.0018752280002445332, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-False]": 0.20086483300019609, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_file[SdkDockerClient-True]": 0.21912105000046722, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-False]": 0.0019577429998207663, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[CmdDockerClient-True]": 0.0018882219997067295, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-False]": 0.19413599700055784, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_start_container_with_stdin_to_stdout[SdkDockerClient-True]": 0.20493547299975035, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[CmdDockerClient]": 0.0019038119999095215, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_exposed_ports[SdkDockerClient]": 0.005358925999644271, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[CmdDockerClient]": 0.0018965889998980856, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_host_network[SdkDockerClient]": 0.030818101000477327, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[CmdDockerClient]": 0.0018259150001540547, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_port_mapping[SdkDockerClient]": 0.02776781500051584, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[CmdDockerClient]": 0.0018280399995092012, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_create_with_volume[SdkDockerClient]": 0.0016781580002316332, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[CmdDockerClient]": 0.0018149349998566322, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_image_names[SdkDockerClient]": 2.0142007899999044, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[CmdDockerClient]": 0.006685124000341602, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_docker_not_available[SdkDockerClient]": 0.0061199229999147065, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[CmdDockerClient]": 0.0018451020000611607, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_error_in_container[SdkDockerClient]": 0.25055487599956905, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[CmdDockerClient]": 0.0018230999999104824, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container[SdkDockerClient]": 0.24426400099991952, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[CmdDockerClient]": 0.0018471160001354292, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_not_running_raises_exception[SdkDockerClient]": 0.03681863600058932, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[CmdDockerClient]": 0.0019019480000679323, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env[SdkDockerClient]": 0.2471831069997279, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[CmdDockerClient]": 0.004067010999733611, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_env_deletion[SdkDockerClient]": 0.28391845900023327, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[CmdDockerClient]": 0.001825013999678049, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin[SdkDockerClient]": 0.24523749999980282, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[CmdDockerClient]": 0.0017984850001084851, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_stdin_stdout_stderr[SdkDockerClient]": 0.24447639800018806, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[CmdDockerClient]": 0.001898541999707959, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_exec_in_container_with_workdir[SdkDockerClient]": 0.2516354889999093, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[CmdDockerClient]": 0.001976046999971004, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command[SdkDockerClient]": 0.00685916200018255, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[CmdDockerClient]": 0.0019165359999533393, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_non_existing_image[SdkDockerClient]": 0.892075214000215, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[CmdDockerClient]": 0.001927016000081494, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_command_not_pulled_image[SdkDockerClient]": 1.8032973970002786, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[CmdDockerClient]": 0.0018309350002709834, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint[SdkDockerClient]": 0.008203754000078334, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[CmdDockerClient]": 0.0017814420002650877, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_non_existing_image[SdkDockerClient]": 0.8991446650002217, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[CmdDockerClient]": 0.0018979609999405511, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_entrypoint_not_pulled_image[SdkDockerClient]": 1.859428914000091, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[CmdDockerClient]": 0.0019298600000183797, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id[SdkDockerClient]": 0.2022854480001115, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[CmdDockerClient]": 0.0017933649996848544, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_id_not_existing[SdkDockerClient]": 0.008695808000084071, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[CmdDockerClient]": 0.0018203959994025354, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip[SdkDockerClient]": 0.1999195009993855, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[CmdDockerClient]": 0.001964907000456151, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_host_network[SdkDockerClient]": 0.04453826700000718, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[CmdDockerClient]": 0.0019503700000313984, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network[SdkDockerClient]": 0.5069378809998852, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[CmdDockerClient]": 0.0019147029997839127, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_non_existent_network[SdkDockerClient]": 0.20092213599946263, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[CmdDockerClient]": 0.0019616019999375567, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_for_network_wrong_network[SdkDockerClient]": 0.3760413859995424, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[CmdDockerClient]": 0.001845012000103452, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_ip_non_existing_container[SdkDockerClient]": 0.007140095999602636, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[CmdDockerClient]": 0.0018087540001943125, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name[SdkDockerClient]": 0.211751970000023, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[CmdDockerClient]": 0.0017725960001371277, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_container_name_not_existing[SdkDockerClient]": 0.00833058499983963, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[CmdDockerClient]": 0.0019355020003786194, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs[SdkDockerClient]": 0.18972093999991557, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[CmdDockerClient]": 0.0019214260005355754, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_logs_non_existent_container[SdkDockerClient]": 0.009277759999804402, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[CmdDockerClient]": 0.0019167969999216439, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network[SdkDockerClient]": 0.03190185600033146, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[CmdDockerClient]": 0.0019102150004073337, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_multiple_networks[SdkDockerClient]": 0.46702226599973073, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[CmdDockerClient]": 0.0019218460001866333, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_network_non_existing_container[SdkDockerClient]": 0.006964829000025929, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[CmdDockerClient]": 0.0017751010000210954, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_id[SdkDockerClient]": 0.022587435000332334, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[CmdDockerClient]": 0.0035851869997713948, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_get_system_info[SdkDockerClient]": 0.027441651999652095, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[CmdDockerClient]": 0.0018600400003379036, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container[SdkDockerClient]": 0.2014164840002195, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[CmdDockerClient]": 0.0017670960000941704, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes[SdkDockerClient]": 0.0017343340000479657, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[CmdDockerClient]": 0.0019259240002611477, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_container_volumes_with_no_volumes[SdkDockerClient]": 0.20137493199990786, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[CmdDockerClient]": 0.0018129719996977656, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_image[SdkDockerClient]": 0.040229618000012124, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[CmdDockerClient]": 0.0018682149998312525, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network[SdkDockerClient]": 0.1394471869998597, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[CmdDockerClient]": 0.0019411520001995086, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_inspect_network_non_existent_network[SdkDockerClient]": 0.0091155160002927, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[CmdDockerClient]": 0.0019454809994385869, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_is_container_running[SdkDockerClient]": 20.4213865480001, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[CmdDockerClient]": 0.0019450590002634272, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers[SdkDockerClient]": 0.08744974899991576, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[CmdDockerClient]": 0.001807411999834585, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter[SdkDockerClient]": 0.08552235900015148, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[CmdDockerClient]": 0.0018285209998794016, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_illegal_filter[SdkDockerClient]": 0.006647383999734302, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[CmdDockerClient]": 0.0017836359993452788, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_filter_non_existing[SdkDockerClient]": 0.007591820000016014, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[CmdDockerClient]": 0.0017439020002711914, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_list_containers_with_podman_image_ref_format[SdkDockerClient]": 0.23641263199988316, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[CmdDockerClient]": 0.0018189339998571086, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pause_non_existing_container[SdkDockerClient]": 0.006975477999731083, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[CmdDockerClient]": 0.0018002070000875392, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image[SdkDockerClient]": 1.7860764469996866, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[CmdDockerClient]": 0.0018617140003698296, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_hash[SdkDockerClient]": 1.3907582980004918, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[CmdDockerClient]": 0.001882252000086737, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_log_handler[SdkDockerClient]": 1.9562445289998323, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[CmdDockerClient]": 0.0019476349998512887, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_docker_image_with_tag[SdkDockerClient]": 2.0829976699997133, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[CmdDockerClient]": 0.0018337100004828244, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_pull_non_existent_docker_image[SdkDockerClient]": 0.8924945820003813, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[CmdDockerClient]": 0.0018987620001098549, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_access_denied[SdkDockerClient]": 2.844253956999637, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[CmdDockerClient]": 0.0017841979997683666, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_invalid_registry[SdkDockerClient]": 0.01628861999961373, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[CmdDockerClient]": 0.0017505140003777342, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_push_non_existent_docker_image[SdkDockerClient]": 0.007904295000571437, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[CmdDockerClient]": 0.001893081999696733, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_remove_non_existing_container[SdkDockerClient]": 0.0059820649998982844, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[CmdDockerClient]": 0.001832890000059706, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_restart_non_existing_container[SdkDockerClient]": 0.006010837999838259, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[CmdDockerClient]": 0.0019302619998597947, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container[SdkDockerClient]": 0.18634989799966206, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[CmdDockerClient]": 0.0017790590004551632, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_automatic_pull[SdkDockerClient]": 2.0497769640001025, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[CmdDockerClient]": 0.0019045529993491073, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_error[SdkDockerClient]": 0.12051284699964526, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[CmdDockerClient]": 0.0017897990001074504, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_non_existent_image[SdkDockerClient]": 0.9058055019995663, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[CmdDockerClient]": 0.0019494580001264694, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_init[SdkDockerClient]": 0.19991828899946995, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[CmdDockerClient]": 0.0019659079998746165, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_container_with_stdin[SdkDockerClient]": 0.1905722890001016, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[CmdDockerClient]": 0.0019390399997973873, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_run_detached_with_logs[SdkDockerClient]": 0.18760319999955755, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[CmdDockerClient]": 0.004199640000479121, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_running_container_names[SdkDockerClient]": 12.021868495000035, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-echo]": 0.0018245429996568419, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[CmdDockerClient-entrypoint1]": 0.001847578000251815, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-echo]": 0.21041104799996901, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_set_container_entrypoint[SdkDockerClient-entrypoint1]": 0.19823402900010478, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[CmdDockerClient]": 0.0018282299997736118, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_start_non_existing_container[SdkDockerClient]": 0.005880053000510088, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[CmdDockerClient]": 0.001892561999738973, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stop_non_existing_container[SdkDockerClient]": 0.007233251000343444, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[CmdDockerClient]": 0.0017612060000828933, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs[SdkDockerClient]": 0.19174634199998764, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[CmdDockerClient]": 0.0019410820004850393, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_stream_logs_non_existent_container[SdkDockerClient]": 0.006470039999385335, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[CmdDockerClient]": 0.001841655999669456, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_image[SdkDockerClient]": 0.1510811149996698, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[CmdDockerClient]": 0.0018297039996468811, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_tag_non_existing_image[SdkDockerClient]": 0.009008295000057842, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[CmdDockerClient]": 0.0018832430000657041, + "tests/integration/docker_utils/test_docker.py::TestDockerClient::test_unpause_non_existing_container[SdkDockerClient]": 0.00609349399974235, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[CmdDockerClient]": 0.005431148999832658, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_creates_image_from_running_container[SdkDockerClient]": 0.6068862429997353, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[CmdDockerClient]": 0.0030486200002997066, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_commit_image_raises_for_nonexistent_container[SdkDockerClient]": 0.0061364139996840095, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[CmdDockerClient]": 0.00305563300025824, + "tests/integration/docker_utils/test_docker.py::TestDockerImages::test_remove_image_raises_for_nonexistent_image[SdkDockerClient]": 0.006206604999988485, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[CmdDockerClient]": 0.003466774999651534, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_create_container_with_labels[SdkDockerClient]": 0.046832280999751674, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[CmdDockerClient]": 0.0018927209998764738, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_get_container_stats[SdkDockerClient]": 1.2184846879999895, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[CmdDockerClient]": 0.0019298810002510436, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_list_containers_with_labels[SdkDockerClient]": 0.19807030699985262, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[CmdDockerClient]": 0.0018792760001815623, + "tests/integration/docker_utils/test_docker.py::TestDockerLabels::test_run_container_with_labels[SdkDockerClient]": 0.1989285150007163, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[CmdDockerClient]": 0.001913229999900068, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_fluentbit[SdkDockerClient]": 5.36594177500001, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[CmdDockerClient]": 0.0033317209999950137, + "tests/integration/docker_utils/test_docker.py::TestDockerLogging::test_docker_logging_none_disables_logs[SdkDockerClient]": 0.19828876500014303, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[CmdDockerClient]": 0.006693457999972452, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network[SdkDockerClient]": 0.4728744529998039, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[CmdDockerClient]": 0.0017990549999922223, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_alias_and_disconnect[SdkDockerClient]": 0.8002853109996977, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[CmdDockerClient]": 0.0019437870000729163, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_network_with_link_local_address[SdkDockerClient]": 0.1945845800000825, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[CmdDockerClient]": 0.0017785260001801362, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_container_to_nonexistent_network[SdkDockerClient]": 0.20232471400004215, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[CmdDockerClient]": 0.001933577000272635, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_connect_nonexistent_container_to_network[SdkDockerClient]": 0.1646526160002395, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[CmdDockerClient]": 0.0018911370002570038, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_container_from_nonexistent_network[SdkDockerClient]": 0.1983018800001446, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[CmdDockerClient]": 0.001956851999693754, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_disconnect_nonexistent_container_from_network[SdkDockerClient]": 0.1513948679994428, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_no_retries": 0.03132584600007249, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_after_init": 1.228974752000795, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_retries_on_init": 1.0455165199996372, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_docker_sdk_timeout_seconds": 0.020637098999941372, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[CmdDockerClient]": 0.0018055779996757337, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_get_container_ip_with_network[SdkDockerClient]": 0.40808642999991207, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[CmdDockerClient]": 0.003235590999793203, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_network_lifecycle[SdkDockerClient]": 0.15269067200006248, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[CmdDockerClient]": 0.001806740000120044, + "tests/integration/docker_utils/test_docker.py::TestDockerNetworking::test_set_container_workdir[SdkDockerClient]": 0.1937682190000487, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[CmdDockerClient]": 0.00347553199981121, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_add[SdkDockerClient]": 0.43650659800005087, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[CmdDockerClient]": 0.0018168599999626167, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_cap_drop[SdkDockerClient]": 0.36540359400032685, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[CmdDockerClient]": 0.0018133219996343541, + "tests/integration/docker_utils/test_docker.py::TestDockerPermissions::test_container_with_sec_opt[SdkDockerClient]": 0.03044557400062331, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-None]": 0.001809424999919429, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-tcp]": 0.0018112679995283543, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[CmdDockerClient-udp]": 0.0017958100002033461, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-None]": 1.5091762609995385, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-tcp]": 1.5143394530005025, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_container_port_can_be_bound[SdkDockerClient-udp]": 1.513704842000152, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-None]": 0.0034692389995143458, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-tcp]": 0.0018458140002621803, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[CmdDockerClient-udp]": 0.0019179689998054528, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-None]": 2.6508907190000173, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-tcp]": 2.6261323010003252, + "tests/integration/docker_utils/test_docker.py::TestDockerPorts::test_reserve_container_port[SdkDockerClient-udp]": 2.8328591720000986, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[CmdDockerClient]": 0.0037117140000191284, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments[SdkDockerClient]": 0.40369018199953643, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-False]": 0.0018236830001114868, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[CmdDockerClient-True]": 0.0018479180002941575, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-False]": 0.1282553200003349, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_dns[SdkDockerClient-True]": 0.11980502500000512, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[CmdDockerClient]": 0.001840493999679893, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_add_host[SdkDockerClient]": 0.21043147699992915, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[CmdDockerClient]": 0.0018838340001821052, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_env_files[SdkDockerClient]": 0.7707681090000733, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[CmdDockerClient]": 0.001984845000151836, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_additional_arguments_random_port[SdkDockerClient]": 0.3941434809994462, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[CmdDockerClient]": 0.007429920999584283, + "tests/integration/docker_utils/test_docker.py::TestRunWithAdditionalArgs::test_run_with_ulimit[SdkDockerClient]": 0.19173134800030311, + "tests/integration/services/test_internal.py::TestHealthResource::test_get": 0.0173249249996843, + "tests/integration/services/test_internal.py::TestHealthResource::test_head": 0.013659120999818697, + "tests/integration/services/test_internal.py::TestInfoEndpoint::test_get": 0.04657253600043987, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[boot-True]": 0.01819560400008413, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[ready-True]": 0.018173092000324687, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[shutdown-False]": 0.018111278000105813, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_individual_stage_completed[start-True]": 0.02438944200048354, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_query_nonexisting_stage": 0.01881593799953407, + "tests/integration/services/test_internal.py::TestInitScriptsResource::test_stages_have_completed": 1.5197968669999682, + "tests/integration/test_config_endpoint.py::test_config_endpoint": 0.039888358000553126, + "tests/integration/test_config_service.py::TestConfigService::test_put_configuration_recorder": 0.1839112790003128, + "tests/integration/test_config_service.py::TestConfigService::test_put_delivery_channel": 0.16544859100031317, + "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher": 0.007018919999609352, + "tests/integration/test_forwarder.py::test_forwarding_fallback_dispatcher_avoid_fallback": 0.004759269999794924, + "tests/integration/test_security.py::TestCSRF::test_CSRF": 0.09418220700081292, + "tests/integration/test_security.py::TestCSRF::test_additional_allowed_origins": 0.016809803000342072, + "tests/integration/test_security.py::TestCSRF::test_cors_apigw_not_applied": 0.04621653299955142, + "tests/integration/test_security.py::TestCSRF::test_cors_s3_override": 0.08517067000002498, + "tests/integration/test_security.py::TestCSRF::test_default_cors_headers": 0.015647554000224773, + "tests/integration/test_security.py::TestCSRF::test_disable_cors_checks": 0.0179948670001977, + "tests/integration/test_security.py::TestCSRF::test_disable_cors_headers": 0.020138906000283896, + "tests/integration/test_security.py::TestCSRF::test_internal_route_cors_headers[/_localstack/health]": 0.011564770999939356, + "tests/integration/test_security.py::TestCSRF::test_no_cors_without_origin_header": 0.015320815000450239, + "tests/integration/test_stores.py::test_nonstandard_regions": 0.1855214690003777, + "tests/integration/utils/test_diagnose.py::test_diagnose_resource": 0.2841156540002885 } diff --git a/Makefile b/Makefile index 53b0f5d732481..6a2b829227317 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,8 @@ freeze: ## Run pip freeze -l in the virtual environment @$(VENV_RUN); pip freeze -l upgrade-pinned-dependencies: venv - $(VENV_RUN); $(PIP_CMD) install --upgrade pip-tools pre-commit + # FIXME remove pin on pip-tools when https://github.com/jazzband/pip-tools/issues/2215 us resolved + $(VENV_RUN); $(PIP_CMD) install --upgrade "pip-tools<7.5.0" pre-commit $(VENV_RUN); pip-compile --strip-extras --upgrade -o requirements-basic.txt pyproject.toml $(VENV_RUN); pip-compile --strip-extras --upgrade --extra runtime -o requirements-runtime.txt pyproject.toml $(VENV_RUN); pip-compile --strip-extras --upgrade --extra test -o requirements-test.txt pyproject.toml diff --git a/README.md b/README.md index a1d16511cfb49..a2cf02d22264a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-:zap: We are thrilled to announce the release of LocalStack 4.6 :zap: +:zap: We are thrilled to announce the release of LocalStack 4.7 :zap:

@@ -93,7 +93,7 @@ Start LocalStack inside a Docker container by running: / /___/ /_/ / /__/ /_/ / /___/ / /_/ /_/ / /__/ ,< /_____/\____/\___/\__,_/_//____/\__/\__,_/\___/_/|_| -- LocalStack CLI: 4.6.0 +- LocalStack CLI: 4.7.0 - Profile: default - App: https://app.localstack.cloud diff --git a/localstack-core/localstack/aws/api/apigateway/__init__.py b/localstack-core/localstack/aws/api/apigateway/__init__.py index 0010dd6b5b24a..ac90e9f542368 100644 --- a/localstack-core/localstack/aws/api/apigateway/__init__.py +++ b/localstack-core/localstack/aws/api/apigateway/__init__.py @@ -289,22 +289,21 @@ class ApiStage(TypedDict, total=False): ListOfARNs = List[ProviderARN] -Authorizer = TypedDict( - "Authorizer", - { - "id": Optional[String], - "name": Optional[String], - "type": Optional[AuthorizerType], - "providerARNs": Optional[ListOfARNs], - "authType": Optional[String], - "authorizerUri": Optional[String], - "authorizerCredentials": Optional[String], - "identitySource": Optional[String], - "identityValidationExpression": Optional[String], - "authorizerResultTtlInSeconds": Optional[NullableInteger], - }, - total=False, -) + + +class Authorizer(TypedDict, total=False): + id: Optional[String] + name: Optional[String] + type: Optional[AuthorizerType] + providerARNs: Optional[ListOfARNs] + authType: Optional[String] + authorizerUri: Optional[String] + authorizerCredentials: Optional[String] + identitySource: Optional[String] + identityValidationExpression: Optional[String] + authorizerResultTtlInSeconds: Optional[NullableInteger] + + ListOfAuthorizer = List[Authorizer] @@ -373,22 +372,17 @@ class CreateApiKeyRequest(ServiceRequest): tags: Optional[MapOfStringToString] -CreateAuthorizerRequest = TypedDict( - "CreateAuthorizerRequest", - { - "restApiId": String, - "name": String, - "type": AuthorizerType, - "providerARNs": Optional[ListOfARNs], - "authType": Optional[String], - "authorizerUri": Optional[String], - "authorizerCredentials": Optional[String], - "identitySource": Optional[String], - "identityValidationExpression": Optional[String], - "authorizerResultTtlInSeconds": Optional[NullableInteger], - }, - total=False, -) +class CreateAuthorizerRequest(TypedDict, total=False): + restApiId: String + name: String + type: AuthorizerType + providerARNs: Optional[ListOfARNs] + authType: Optional[String] + authorizerUri: Optional[String] + authorizerCredentials: Optional[String] + identitySource: Optional[String] + identityValidationExpression: Optional[String] + authorizerResultTtlInSeconds: Optional[NullableInteger] class CreateBasePathMappingRequest(ServiceRequest): @@ -417,17 +411,12 @@ class CreateDeploymentRequest(ServiceRequest): tracingEnabled: Optional[NullableBoolean] -DocumentationPartLocation = TypedDict( - "DocumentationPartLocation", - { - "type": DocumentationPartType, - "path": Optional[String], - "method": Optional[String], - "statusCode": Optional[DocumentationPartLocationStatusCode], - "name": Optional[String], - }, - total=False, -) +class DocumentationPartLocation(TypedDict, total=False): + type: DocumentationPartType + path: Optional[String] + method: Optional[String] + statusCode: Optional[DocumentationPartLocationStatusCode] + name: Optional[String] class CreateDocumentationPartRequest(ServiceRequest): @@ -889,19 +878,14 @@ class GetDocumentationPartRequest(ServiceRequest): documentationPartId: String -GetDocumentationPartsRequest = TypedDict( - "GetDocumentationPartsRequest", - { - "restApiId": String, - "type": Optional[DocumentationPartType], - "nameQuery": Optional[String], - "path": Optional[String], - "position": Optional[String], - "limit": Optional[NullableInteger], - "locationStatus": Optional[LocationStatusType], - }, - total=False, -) +class GetDocumentationPartsRequest(TypedDict, total=False): + restApiId: String + type: Optional[DocumentationPartType] + nameQuery: Optional[String] + path: Optional[String] + position: Optional[String] + limit: Optional[NullableInteger] + locationStatus: Optional[LocationStatusType] class GetDocumentationVersionRequest(ServiceRequest): @@ -1131,27 +1115,26 @@ class IntegrationResponse(TypedDict, total=False): MapOfIntegrationResponse = Dict[String, IntegrationResponse] -Integration = TypedDict( - "Integration", - { - "type": Optional[IntegrationType], - "httpMethod": Optional[String], - "uri": Optional[String], - "connectionType": Optional[ConnectionType], - "connectionId": Optional[String], - "credentials": Optional[String], - "requestParameters": Optional[MapOfStringToString], - "requestTemplates": Optional[MapOfStringToString], - "passthroughBehavior": Optional[String], - "contentHandling": Optional[ContentHandlingStrategy], - "timeoutInMillis": Optional[Integer], - "cacheNamespace": Optional[String], - "cacheKeyParameters": Optional[ListOfString], - "integrationResponses": Optional[MapOfIntegrationResponse], - "tlsConfig": Optional[TlsConfig], - }, - total=False, -) + + +class Integration(TypedDict, total=False): + type: Optional[IntegrationType] + httpMethod: Optional[String] + uri: Optional[String] + connectionType: Optional[ConnectionType] + connectionId: Optional[String] + credentials: Optional[String] + requestParameters: Optional[MapOfStringToString] + requestTemplates: Optional[MapOfStringToString] + passthroughBehavior: Optional[String] + contentHandling: Optional[ContentHandlingStrategy] + timeoutInMillis: Optional[Integer] + cacheNamespace: Optional[String] + cacheKeyParameters: Optional[ListOfString] + integrationResponses: Optional[MapOfIntegrationResponse] + tlsConfig: Optional[TlsConfig] + + Long = int ListOfLong = List[Long] @@ -1319,16 +1302,15 @@ class UsagePlan(TypedDict, total=False): ListOfUsagePlan = List[UsagePlan] -UsagePlanKey = TypedDict( - "UsagePlanKey", - { - "id": Optional[String], - "type": Optional[String], - "value": Optional[String], - "name": Optional[String], - }, - total=False, -) + + +class UsagePlanKey(TypedDict, total=False): + id: Optional[String] + type: Optional[String] + value: Optional[String] + name: Optional[String] + + ListOfUsagePlanKey = List[UsagePlanKey] @@ -1360,29 +1342,24 @@ class PutGatewayResponseRequest(ServiceRequest): responseTemplates: Optional[MapOfStringToString] -PutIntegrationRequest = TypedDict( - "PutIntegrationRequest", - { - "restApiId": String, - "resourceId": String, - "httpMethod": String, - "type": IntegrationType, - "integrationHttpMethod": Optional[String], - "uri": Optional[String], - "connectionType": Optional[ConnectionType], - "connectionId": Optional[String], - "credentials": Optional[String], - "requestParameters": Optional[MapOfStringToString], - "requestTemplates": Optional[MapOfStringToString], - "passthroughBehavior": Optional[String], - "cacheNamespace": Optional[String], - "cacheKeyParameters": Optional[ListOfString], - "contentHandling": Optional[ContentHandlingStrategy], - "timeoutInMillis": Optional[NullableInteger], - "tlsConfig": Optional[TlsConfig], - }, - total=False, -) +class PutIntegrationRequest(TypedDict, total=False): + restApiId: String + resourceId: String + httpMethod: String + type: IntegrationType + integrationHttpMethod: Optional[String] + uri: Optional[String] + connectionType: Optional[ConnectionType] + connectionId: Optional[String] + credentials: Optional[String] + requestParameters: Optional[MapOfStringToString] + requestTemplates: Optional[MapOfStringToString] + passthroughBehavior: Optional[String] + cacheNamespace: Optional[String] + cacheKeyParameters: Optional[ListOfString] + contentHandling: Optional[ContentHandlingStrategy] + timeoutInMillis: Optional[NullableInteger] + tlsConfig: Optional[TlsConfig] class PutIntegrationResponseRequest(ServiceRequest): diff --git a/localstack-core/localstack/aws/api/core.py b/localstack-core/localstack/aws/api/core.py index dbe32d7973284..803567e78806a 100644 --- a/localstack-core/localstack/aws/api/core.py +++ b/localstack-core/localstack/aws/api/core.py @@ -1,11 +1,10 @@ import functools +from collections.abc import Callable from typing import ( Any, - Callable, NamedTuple, ParamSpec, Protocol, - Type, TypedDict, TypeVar, ) @@ -67,7 +66,7 @@ def __init__(self, code: str, message: str, status_code: int = 400, sender_fault self.sender_fault = sender_fault -Operation = Type[ServiceRequest] +Operation = type[ServiceRequest] class ServiceOperation(NamedTuple): diff --git a/localstack-core/localstack/aws/api/ec2/__init__.py b/localstack-core/localstack/aws/api/ec2/__init__.py index 790caa860c571..24556d57595a0 100644 --- a/localstack-core/localstack/aws/api/ec2/__init__.py +++ b/localstack-core/localstack/aws/api/ec2/__init__.py @@ -9113,6 +9113,7 @@ class Route(TypedDict, total=False): VpcPeeringConnectionId: Optional[String] CoreNetworkArn: Optional[CoreNetworkArn] OdbNetworkArn: Optional[OdbNetworkArn] + IpAddress: Optional[String] RouteList = List[Route] @@ -20678,6 +20679,7 @@ class TerminateClientVpnConnectionsResult(TypedDict, total=False): class TerminateInstancesRequest(ServiceRequest): InstanceIds: InstanceIdStringList + Force: Optional[Boolean] SkipOsShutdown: Optional[Boolean] DryRun: Optional[Boolean] @@ -29116,6 +29118,7 @@ def terminate_instances( self, context: RequestContext, instance_ids: InstanceIdStringList, + force: Boolean | None = None, skip_os_shutdown: Boolean | None = None, dry_run: Boolean | None = None, **kwargs, diff --git a/localstack-core/localstack/aws/api/events/__init__.py b/localstack-core/localstack/aws/api/events/__init__.py index ced78055f6c25..f62bb74e14457 100644 --- a/localstack-core/localstack/aws/api/events/__init__.py +++ b/localstack-core/localstack/aws/api/events/__init__.py @@ -893,23 +893,19 @@ class DisableRuleRequest(ServiceRequest): EventBusName: Optional[EventBusNameOrArn] -PlacementStrategy = TypedDict( - "PlacementStrategy", - { - "type": Optional[PlacementStrategyType], - "field": Optional[PlacementStrategyField], - }, - total=False, -) +class PlacementStrategy(TypedDict, total=False): + type: Optional[PlacementStrategyType] + field: Optional[PlacementStrategyField] + + PlacementStrategies = List[PlacementStrategy] -PlacementConstraint = TypedDict( - "PlacementConstraint", - { - "type": Optional[PlacementConstraintType], - "expression": Optional[PlacementConstraintExpression], - }, - total=False, -) + + +class PlacementConstraint(TypedDict, total=False): + type: Optional[PlacementConstraintType] + expression: Optional[PlacementConstraintExpression] + + PlacementConstraints = List[PlacementConstraint] diff --git a/localstack-core/localstack/aws/api/logs/__init__.py b/localstack-core/localstack/aws/api/logs/__init__.py index 1f7d7e407221d..a47bc36320bfd 100644 --- a/localstack-core/localstack/aws/api/logs/__init__.py +++ b/localstack-core/localstack/aws/api/logs/__init__.py @@ -1585,14 +1585,11 @@ class UpperCaseString(TypedDict, total=False): withKeys: UpperCaseStringWithKeys -TypeConverterEntry = TypedDict( - "TypeConverterEntry", - { - "key": Key, - "type": Type, - }, - total=False, -) +class TypeConverterEntry(TypedDict, total=False): + key: Key + type: Type + + TypeConverterEntries = List[TypeConverterEntry] diff --git a/localstack-core/localstack/aws/api/opensearch/__init__.py b/localstack-core/localstack/aws/api/opensearch/__init__.py index 29ed8459808e8..2e97a7ad84e7e 100644 --- a/localstack-core/localstack/aws/api/opensearch/__init__.py +++ b/localstack-core/localstack/aws/api/opensearch/__init__.py @@ -39,6 +39,8 @@ ErrorType = str GUID = str HostedZoneId = str +IAMFederationRolesKey = str +IAMFederationSubjectKey = str Id = str IdentityCenterApplicationARN = str IdentityCenterInstanceARN = str @@ -836,6 +838,12 @@ class AdvancedOptionsStatus(TypedDict, total=False): DisableTimestamp = datetime +class IAMFederationOptionsOutput(TypedDict, total=False): + Enabled: Optional[Boolean] + SubjectKey: Optional[IAMFederationSubjectKey] + RolesKey: Optional[IAMFederationRolesKey] + + class JWTOptionsOutput(TypedDict, total=False): Enabled: Optional[Boolean] SubjectKey: Optional[String] @@ -861,10 +869,17 @@ class AdvancedSecurityOptions(TypedDict, total=False): InternalUserDatabaseEnabled: Optional[Boolean] SAMLOptions: Optional[SAMLOptionsOutput] JWTOptions: Optional[JWTOptionsOutput] + IAMFederationOptions: Optional[IAMFederationOptionsOutput] AnonymousAuthDisableDate: Optional[DisableTimestamp] AnonymousAuthEnabled: Optional[Boolean] +class IAMFederationOptionsInput(TypedDict, total=False): + Enabled: Optional[Boolean] + SubjectKey: Optional[IAMFederationSubjectKey] + RolesKey: Optional[IAMFederationRolesKey] + + class JWTOptionsInput(TypedDict, total=False): Enabled: Optional[Boolean] SubjectKey: Optional[SubjectKey] @@ -894,6 +909,7 @@ class AdvancedSecurityOptionsInput(TypedDict, total=False): MasterUserOptions: Optional[MasterUserOptions] SAMLOptions: Optional[SAMLOptionsInput] JWTOptions: Optional[JWTOptionsInput] + IAMFederationOptions: Optional[IAMFederationOptionsInput] AnonymousAuthEnabled: Optional[Boolean] diff --git a/localstack-core/localstack/aws/api/pipes/__init__.py b/localstack-core/localstack/aws/api/pipes/__init__.py index 6fe68d846fa23..48ae582ede810 100644 --- a/localstack-core/localstack/aws/api/pipes/__init__.py +++ b/localstack-core/localstack/aws/api/pipes/__init__.py @@ -511,23 +511,19 @@ class EcsEphemeralStorage(TypedDict, total=False): sizeInGiB: EphemeralStorageSize -EcsResourceRequirement = TypedDict( - "EcsResourceRequirement", - { - "type": EcsResourceRequirementType, - "value": String, - }, - total=False, -) +class EcsResourceRequirement(TypedDict, total=False): + type: EcsResourceRequirementType + value: String + + EcsResourceRequirementsList = List[EcsResourceRequirement] -EcsEnvironmentFile = TypedDict( - "EcsEnvironmentFile", - { - "type": EcsEnvironmentFileType, - "value": String, - }, - total=False, -) + + +class EcsEnvironmentFile(TypedDict, total=False): + type: EcsEnvironmentFileType + value: String + + EcsEnvironmentFileList = List[EcsEnvironmentFile] @@ -563,23 +559,19 @@ class EcsTaskOverride(TypedDict, total=False): TaskRoleArn: Optional[ArnOrJsonPath] -PlacementStrategy = TypedDict( - "PlacementStrategy", - { - "type": Optional[PlacementStrategyType], - "field": Optional[PlacementStrategyField], - }, - total=False, -) +class PlacementStrategy(TypedDict, total=False): + type: Optional[PlacementStrategyType] + field: Optional[PlacementStrategyField] + + PlacementStrategies = List[PlacementStrategy] -PlacementConstraint = TypedDict( - "PlacementConstraint", - { - "type": Optional[PlacementConstraintType], - "expression": Optional[PlacementConstraintExpression], - }, - total=False, -) + + +class PlacementConstraint(TypedDict, total=False): + type: Optional[PlacementConstraintType] + expression: Optional[PlacementConstraintExpression] + + PlacementConstraints = List[PlacementConstraint] diff --git a/localstack-core/localstack/aws/api/s3control/__init__.py b/localstack-core/localstack/aws/api/s3control/__init__.py index 429e3219630d2..c1b7d24099e73 100644 --- a/localstack-core/localstack/aws/api/s3control/__init__.py +++ b/localstack-core/localstack/aws/api/s3control/__init__.py @@ -857,6 +857,7 @@ class CreateAccessPointRequest(ServiceRequest): PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] BucketAccountId: Optional[AccountId] Scope: Optional[Scope] + Tags: Optional[TagList] class CreateAccessPointResult(TypedDict, total=False): @@ -2423,6 +2424,7 @@ def create_access_point( public_access_block_configuration: PublicAccessBlockConfiguration | None = None, bucket_account_id: AccountId | None = None, scope: Scope | None = None, + tags: TagList | None = None, **kwargs, ) -> CreateAccessPointResult: raise NotImplementedError diff --git a/localstack-core/localstack/aws/api/scheduler/__init__.py b/localstack-core/localstack/aws/api/scheduler/__init__.py index 696814447cd11..829a895bd4a06 100644 --- a/localstack-core/localstack/aws/api/scheduler/__init__.py +++ b/localstack-core/localstack/aws/api/scheduler/__init__.py @@ -201,23 +201,21 @@ class EventBridgeParameters(TypedDict, total=False): TagMap = Dict[TagKey, TagValue] Tags = List[TagMap] -PlacementStrategy = TypedDict( - "PlacementStrategy", - { - "field": Optional[PlacementStrategyField], - "type": Optional[PlacementStrategyType], - }, - total=False, -) + + +class PlacementStrategy(TypedDict, total=False): + field: Optional[PlacementStrategyField] + type: Optional[PlacementStrategyType] + + PlacementStrategies = List[PlacementStrategy] -PlacementConstraint = TypedDict( - "PlacementConstraint", - { - "expression": Optional[PlacementConstraintExpression], - "type": Optional[PlacementConstraintType], - }, - total=False, -) + + +class PlacementConstraint(TypedDict, total=False): + expression: Optional[PlacementConstraintExpression] + type: Optional[PlacementConstraintType] + + PlacementConstraints = List[PlacementConstraint] diff --git a/localstack-core/localstack/aws/api/stepfunctions/__init__.py b/localstack-core/localstack/aws/api/stepfunctions/__init__.py index c1dca160d5ffe..ee3c57177ab95 100644 --- a/localstack-core/localstack/aws/api/stepfunctions/__init__.py +++ b/localstack-core/localstack/aws/api/stepfunctions/__init__.py @@ -501,15 +501,10 @@ class CloudWatchLogsLogGroup(TypedDict, total=False): logGroupArn: Optional[Arn] -EncryptionConfiguration = TypedDict( - "EncryptionConfiguration", - { - "kmsKeyId": Optional[KmsKeyId], - "kmsDataKeyReusePeriodSeconds": Optional[KmsDataKeyReusePeriodSeconds], - "type": EncryptionType, - }, - total=False, -) +class EncryptionConfiguration(TypedDict, total=False): + kmsKeyId: Optional[KmsKeyId] + kmsDataKeyReusePeriodSeconds: Optional[KmsDataKeyReusePeriodSeconds] + type: EncryptionType class Tag(TypedDict, total=False): @@ -567,22 +562,17 @@ class LoggingConfiguration(TypedDict, total=False): destinations: Optional[LogDestinationList] -CreateStateMachineInput = TypedDict( - "CreateStateMachineInput", - { - "name": Name, - "definition": Definition, - "roleArn": Arn, - "type": Optional[StateMachineType], - "loggingConfiguration": Optional[LoggingConfiguration], - "tags": Optional[TagList], - "tracingConfiguration": Optional[TracingConfiguration], - "publish": Optional[Publish], - "versionDescription": Optional[VersionDescription], - "encryptionConfiguration": Optional[EncryptionConfiguration], - }, - total=False, -) +class CreateStateMachineInput(TypedDict, total=False): + name: Name + definition: Definition + roleArn: Arn + type: Optional[StateMachineType] + loggingConfiguration: Optional[LoggingConfiguration] + tags: Optional[TagList] + tracingConfiguration: Optional[TracingConfiguration] + publish: Optional[Publish] + versionDescription: Optional[VersionDescription] + encryptionConfiguration: Optional[EncryptionConfiguration] class CreateStateMachineOutput(TypedDict, total=False): @@ -756,26 +746,21 @@ class DescribeStateMachineInput(ServiceRequest): includedData: Optional[IncludedData] -DescribeStateMachineOutput = TypedDict( - "DescribeStateMachineOutput", - { - "stateMachineArn": Arn, - "name": Name, - "status": Optional[StateMachineStatus], - "definition": Definition, - "roleArn": Arn, - "type": StateMachineType, - "creationDate": Timestamp, - "loggingConfiguration": Optional[LoggingConfiguration], - "tracingConfiguration": Optional[TracingConfiguration], - "label": Optional[MapRunLabel], - "revisionId": Optional[RevisionId], - "description": Optional[VersionDescription], - "encryptionConfiguration": Optional[EncryptionConfiguration], - "variableReferences": Optional[VariableReferences], - }, - total=False, -) +class DescribeStateMachineOutput(TypedDict, total=False): + stateMachineArn: Arn + name: Name + status: Optional[StateMachineStatus] + definition: Definition + roleArn: Arn + type: StateMachineType + creationDate: Timestamp + loggingConfiguration: Optional[LoggingConfiguration] + tracingConfiguration: Optional[TracingConfiguration] + label: Optional[MapRunLabel] + revisionId: Optional[RevisionId] + description: Optional[VersionDescription] + encryptionConfiguration: Optional[EncryptionConfiguration] + variableReferences: Optional[VariableReferences] class EvaluationFailedEventDetails(TypedDict, total=False): @@ -987,55 +972,50 @@ class TaskFailedEventDetails(TypedDict, total=False): cause: Optional[SensitiveCause] -HistoryEvent = TypedDict( - "HistoryEvent", - { - "timestamp": Timestamp, - "type": HistoryEventType, - "id": EventId, - "previousEventId": Optional[EventId], - "activityFailedEventDetails": Optional[ActivityFailedEventDetails], - "activityScheduleFailedEventDetails": Optional[ActivityScheduleFailedEventDetails], - "activityScheduledEventDetails": Optional[ActivityScheduledEventDetails], - "activityStartedEventDetails": Optional[ActivityStartedEventDetails], - "activitySucceededEventDetails": Optional[ActivitySucceededEventDetails], - "activityTimedOutEventDetails": Optional[ActivityTimedOutEventDetails], - "taskFailedEventDetails": Optional[TaskFailedEventDetails], - "taskScheduledEventDetails": Optional[TaskScheduledEventDetails], - "taskStartFailedEventDetails": Optional[TaskStartFailedEventDetails], - "taskStartedEventDetails": Optional[TaskStartedEventDetails], - "taskSubmitFailedEventDetails": Optional[TaskSubmitFailedEventDetails], - "taskSubmittedEventDetails": Optional[TaskSubmittedEventDetails], - "taskSucceededEventDetails": Optional[TaskSucceededEventDetails], - "taskTimedOutEventDetails": Optional[TaskTimedOutEventDetails], - "executionFailedEventDetails": Optional[ExecutionFailedEventDetails], - "executionStartedEventDetails": Optional[ExecutionStartedEventDetails], - "executionSucceededEventDetails": Optional[ExecutionSucceededEventDetails], - "executionAbortedEventDetails": Optional[ExecutionAbortedEventDetails], - "executionTimedOutEventDetails": Optional[ExecutionTimedOutEventDetails], - "executionRedrivenEventDetails": Optional[ExecutionRedrivenEventDetails], - "mapStateStartedEventDetails": Optional[MapStateStartedEventDetails], - "mapIterationStartedEventDetails": Optional[MapIterationEventDetails], - "mapIterationSucceededEventDetails": Optional[MapIterationEventDetails], - "mapIterationFailedEventDetails": Optional[MapIterationEventDetails], - "mapIterationAbortedEventDetails": Optional[MapIterationEventDetails], - "lambdaFunctionFailedEventDetails": Optional[LambdaFunctionFailedEventDetails], - "lambdaFunctionScheduleFailedEventDetails": Optional[ - LambdaFunctionScheduleFailedEventDetails - ], - "lambdaFunctionScheduledEventDetails": Optional[LambdaFunctionScheduledEventDetails], - "lambdaFunctionStartFailedEventDetails": Optional[LambdaFunctionStartFailedEventDetails], - "lambdaFunctionSucceededEventDetails": Optional[LambdaFunctionSucceededEventDetails], - "lambdaFunctionTimedOutEventDetails": Optional[LambdaFunctionTimedOutEventDetails], - "stateEnteredEventDetails": Optional[StateEnteredEventDetails], - "stateExitedEventDetails": Optional[StateExitedEventDetails], - "mapRunStartedEventDetails": Optional[MapRunStartedEventDetails], - "mapRunFailedEventDetails": Optional[MapRunFailedEventDetails], - "mapRunRedrivenEventDetails": Optional[MapRunRedrivenEventDetails], - "evaluationFailedEventDetails": Optional[EvaluationFailedEventDetails], - }, - total=False, -) +class HistoryEvent(TypedDict, total=False): + timestamp: Timestamp + type: HistoryEventType + id: EventId + previousEventId: Optional[EventId] + activityFailedEventDetails: Optional[ActivityFailedEventDetails] + activityScheduleFailedEventDetails: Optional[ActivityScheduleFailedEventDetails] + activityScheduledEventDetails: Optional[ActivityScheduledEventDetails] + activityStartedEventDetails: Optional[ActivityStartedEventDetails] + activitySucceededEventDetails: Optional[ActivitySucceededEventDetails] + activityTimedOutEventDetails: Optional[ActivityTimedOutEventDetails] + taskFailedEventDetails: Optional[TaskFailedEventDetails] + taskScheduledEventDetails: Optional[TaskScheduledEventDetails] + taskStartFailedEventDetails: Optional[TaskStartFailedEventDetails] + taskStartedEventDetails: Optional[TaskStartedEventDetails] + taskSubmitFailedEventDetails: Optional[TaskSubmitFailedEventDetails] + taskSubmittedEventDetails: Optional[TaskSubmittedEventDetails] + taskSucceededEventDetails: Optional[TaskSucceededEventDetails] + taskTimedOutEventDetails: Optional[TaskTimedOutEventDetails] + executionFailedEventDetails: Optional[ExecutionFailedEventDetails] + executionStartedEventDetails: Optional[ExecutionStartedEventDetails] + executionSucceededEventDetails: Optional[ExecutionSucceededEventDetails] + executionAbortedEventDetails: Optional[ExecutionAbortedEventDetails] + executionTimedOutEventDetails: Optional[ExecutionTimedOutEventDetails] + executionRedrivenEventDetails: Optional[ExecutionRedrivenEventDetails] + mapStateStartedEventDetails: Optional[MapStateStartedEventDetails] + mapIterationStartedEventDetails: Optional[MapIterationEventDetails] + mapIterationSucceededEventDetails: Optional[MapIterationEventDetails] + mapIterationFailedEventDetails: Optional[MapIterationEventDetails] + mapIterationAbortedEventDetails: Optional[MapIterationEventDetails] + lambdaFunctionFailedEventDetails: Optional[LambdaFunctionFailedEventDetails] + lambdaFunctionScheduleFailedEventDetails: Optional[LambdaFunctionScheduleFailedEventDetails] + lambdaFunctionScheduledEventDetails: Optional[LambdaFunctionScheduledEventDetails] + lambdaFunctionStartFailedEventDetails: Optional[LambdaFunctionStartFailedEventDetails] + lambdaFunctionSucceededEventDetails: Optional[LambdaFunctionSucceededEventDetails] + lambdaFunctionTimedOutEventDetails: Optional[LambdaFunctionTimedOutEventDetails] + stateEnteredEventDetails: Optional[StateEnteredEventDetails] + stateExitedEventDetails: Optional[StateExitedEventDetails] + mapRunStartedEventDetails: Optional[MapRunStartedEventDetails] + mapRunFailedEventDetails: Optional[MapRunFailedEventDetails] + mapRunRedrivenEventDetails: Optional[MapRunRedrivenEventDetails] + evaluationFailedEventDetails: Optional[EvaluationFailedEventDetails] + + HistoryEventList = List[HistoryEvent] @@ -1162,16 +1142,13 @@ class ListStateMachinesInput(ServiceRequest): nextToken: Optional[PageToken] -StateMachineListItem = TypedDict( - "StateMachineListItem", - { - "stateMachineArn": Arn, - "name": Name, - "type": StateMachineType, - "creationDate": Timestamp, - }, - total=False, -) +class StateMachineListItem(TypedDict, total=False): + stateMachineArn: Arn + name: Name + type: StateMachineType + creationDate: Timestamp + + StateMachineList = List[StateMachineListItem] @@ -1367,16 +1344,13 @@ class ValidateStateMachineDefinitionDiagnostic(TypedDict, total=False): ValidateStateMachineDefinitionDiagnosticList = List[ValidateStateMachineDefinitionDiagnostic] -ValidateStateMachineDefinitionInput = TypedDict( - "ValidateStateMachineDefinitionInput", - { - "definition": Definition, - "type": Optional[StateMachineType], - "severity": Optional[ValidateStateMachineDefinitionSeverity], - "maxResults": Optional[ValidateStateMachineDefinitionMaxResult], - }, - total=False, -) + + +class ValidateStateMachineDefinitionInput(TypedDict, total=False): + definition: Definition + type: Optional[StateMachineType] + severity: Optional[ValidateStateMachineDefinitionSeverity] + maxResults: Optional[ValidateStateMachineDefinitionMaxResult] class ValidateStateMachineDefinitionOutput(TypedDict, total=False): diff --git a/localstack-core/localstack/aws/api/support/__init__.py b/localstack-core/localstack/aws/api/support/__init__.py index c1575127c69e6..af48bc4abd8a4 100644 --- a/localstack-core/localstack/aws/api/support/__init__.py +++ b/localstack-core/localstack/aws/api/support/__init__.py @@ -209,15 +209,14 @@ class SupportedHour(TypedDict, total=False): SupportedHoursList = List[SupportedHour] -CommunicationTypeOptions = TypedDict( - "CommunicationTypeOptions", - { - "type": Optional[Type], - "supportedHours": Optional[SupportedHoursList], - "datesWithoutSupport": Optional[DatesWithoutSupportList], - }, - total=False, -) + + +class CommunicationTypeOptions(TypedDict, total=False): + type: Optional[Type] + supportedHours: Optional[SupportedHoursList] + datesWithoutSupport: Optional[DatesWithoutSupportList] + + CommunicationTypeOptionsList = List[CommunicationTypeOptions] diff --git a/localstack-core/localstack/aws/chain.py b/localstack-core/localstack/aws/chain.py index 6702d154cefaf..8a371cf0d945c 100644 --- a/localstack-core/localstack/aws/chain.py +++ b/localstack-core/localstack/aws/chain.py @@ -5,7 +5,7 @@ from __future__ import annotations import logging -from typing import Callable, Type +from collections.abc import Callable from rolo.gateway import ( CompositeExceptionHandler, @@ -29,7 +29,7 @@ was raised by the request handler, the RequestContext, and the Response object to be populated.""" -HandlerChain: Type[RoloHandlerChain[RequestContext]] = RoloHandlerChain +HandlerChain: type[RoloHandlerChain[RequestContext]] = RoloHandlerChain __all__ = [ "HandlerChain", diff --git a/localstack-core/localstack/aws/client.py b/localstack-core/localstack/aws/client.py index 6d938c086a8cf..b372d8c2f5914 100644 --- a/localstack-core/localstack/aws/client.py +++ b/localstack-core/localstack/aws/client.py @@ -2,8 +2,8 @@ import io import logging -from datetime import datetime, timezone -from typing import Dict, Iterable, Optional +from collections.abc import Iterable +from datetime import UTC, datetime from urllib.parse import urlsplit from botocore import awsrequest @@ -130,8 +130,8 @@ def close(self): def _add_modeled_error_fields( - response_dict: Dict, - parsed_response: Dict, + response_dict: dict, + parsed_response: dict, operation_model: OperationModel, parser: ResponseParser, ): @@ -217,7 +217,7 @@ def _patched_decode_epoch_datetime(self) -> datetime: # AWS breaks the CBOR spec by using the millis (instead of seconds with floating point support for millis) # https://github.com/aws/aws-sdk-java-v2/issues/4661 value = value / 1000 - tmp = datetime.fromtimestamp(value, timezone.utc) + tmp = datetime.fromtimestamp(value, UTC) except (OverflowError, OSError, ValueError) as exc: raise CBORDecodeValueError("error decoding datetime from epoch") from exc @@ -335,9 +335,7 @@ def parse_response( return parsed_response -def parse_service_exception( - response: Response, parsed_response: Dict -) -> Optional[ServiceException]: +def parse_service_exception(response: Response, parsed_response: dict) -> ServiceException | None: """ Creates a ServiceException (one ASF can handle) from a parsed response (one that botocore would return). It does not automatically raise the exception (see #raise_service_exception). @@ -363,7 +361,7 @@ def parse_service_exception( return service_exception -def raise_service_exception(response: Response, parsed_response: Dict) -> None: +def raise_service_exception(response: Response, parsed_response: dict) -> None: """ Creates and raises a ServiceException from a parsed response (one that botocore would return). :param response: Un-parsed response diff --git a/localstack-core/localstack/aws/connect.py b/localstack-core/localstack/aws/connect.py index 6a04285e021a2..7c7aeb22282f4 100644 --- a/localstack-core/localstack/aws/connect.py +++ b/localstack-core/localstack/aws/connect.py @@ -10,10 +10,11 @@ import re import threading from abc import ABC, abstractmethod +from collections.abc import Callable from functools import lru_cache, partial from random import choice from socket import socket -from typing import Any, Callable, Generic, Optional, TypedDict, TypeVar +from typing import Any, Generic, TypedDict, TypeVar import dns.message import dns.query @@ -88,8 +89,8 @@ def make_hash(o): return hash(frozenset(sorted(new_o.items()))) -def config_equality_patch(self, other: object): - return type(self) == type(other) and self._user_provided_options == other._user_provided_options +def config_equality_patch(self, other: object) -> bool: + return type(self) is type(other) and self._user_provided_options == other._user_provided_options def config_hash_patch(self): @@ -277,10 +278,10 @@ def __init__( def __call__( self, *, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, endpoint_url: str = None, config: Config = None, ) -> ServiceLevelClientFactory: @@ -318,11 +319,11 @@ def with_assumed_role( self, *, role_arn: str, - service_principal: Optional[ServicePrincipal] = None, - session_name: Optional[str] = None, - region_name: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + service_principal: ServicePrincipal | None = None, + session_name: str | None = None, + region_name: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> ServiceLevelClientFactory: """ Create a service level client factory with credentials from assuming the given role ARN. @@ -362,12 +363,12 @@ def with_assumed_role( def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ): raise NotImplementedError() @@ -389,11 +390,11 @@ def _get_client( service_name: str, region_name: str, use_ssl: bool, - verify: Optional[bool], - endpoint_url: Optional[str], - aws_access_key_id: Optional[str], - aws_secret_access_key: Optional[str], - aws_session_token: Optional[str], + verify: bool | None, + endpoint_url: str | None, + aws_access_key_id: str | None, + aws_secret_access_key: str | None, + aws_session_token: str | None, config: Config, ) -> BaseClient: """ @@ -480,12 +481,12 @@ def _get_client_post_hook(self, client: BaseClient) -> BaseClient: def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> BaseClient: """ Build and return client for connections originating within LocalStack. @@ -535,12 +536,12 @@ class ExternalClientFactory(ClientFactory): def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> BaseClient: """ Build and return client for connections originating outside LocalStack and targeting Localstack. @@ -604,12 +605,12 @@ class ExternalAwsClientFactory(ClientFactory): def get_client( self, service_name: str, - region_name: Optional[str] = None, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - aws_session_token: Optional[str] = None, - endpoint_url: Optional[str] = None, - config: Optional[Config] = None, + region_name: str | None = None, + aws_access_key_id: str | None = None, + aws_secret_access_key: str | None = None, + aws_session_token: str | None = None, + endpoint_url: str | None = None, + config: Config | None = None, ) -> BaseClient: """ Build and return client for connections originating outside LocalStack and targeting AWS. diff --git a/localstack-core/localstack/aws/forwarder.py b/localstack-core/localstack/aws/forwarder.py index c25d4b90f6c09..f368f3f1ebce9 100644 --- a/localstack-core/localstack/aws/forwarder.py +++ b/localstack-core/localstack/aws/forwarder.py @@ -3,7 +3,8 @@ DynamoDBLocal) from a service provider. """ -from typing import Any, Callable, Mapping, Optional, Union +from collections.abc import Callable, Mapping +from typing import Any from botocore.awsrequest import AWSPreparedRequest, prepare_request_dict from botocore.config import Config as BotoConfig @@ -54,7 +55,7 @@ def __call__( self, context: RequestContext, service_request: ServiceRequest = None, - ) -> Optional[Union[ServiceResponse, Response]]: + ) -> ServiceResponse | Response | None: """Method to satisfy the ``ServiceRequestHandler`` protocol.""" return self.forward(context, service_request) @@ -62,7 +63,7 @@ def forward( self, context: RequestContext, service_request: ServiceRequest = None, - ) -> Optional[Union[ServiceResponse, Response]]: + ) -> ServiceResponse | Response | None: """ Forwards the given request to the backend configured by ``endpoint_url``. @@ -197,7 +198,7 @@ def create_aws_request_context( action: str, parameters: Mapping[str, Any] = None, region: str = None, - endpoint_url: Optional[str] = None, + endpoint_url: str | None = None, ) -> RequestContext: """ This is a stripped-down version of what the botocore client does to perform an HTTP request from a client call. A diff --git a/localstack-core/localstack/aws/gateway.py b/localstack-core/localstack/aws/gateway.py index 6fd526b6014fc..34959d6d2d0b7 100644 --- a/localstack-core/localstack/aws/gateway.py +++ b/localstack-core/localstack/aws/gateway.py @@ -1,5 +1,3 @@ -import typing as t - from rolo.gateway import Gateway as RoloGateway from rolo.response import Response @@ -17,7 +15,7 @@ def __init__( response_handlers: list[Handler] = None, finalizers: list[Handler] = None, exception_handlers: list[ExceptionHandler] = None, - context_class: t.Type[RequestContext] = None, + context_class: type[RequestContext] = None, ) -> None: super().__init__( request_handlers, diff --git a/localstack-core/localstack/aws/handlers/analytics.py b/localstack-core/localstack/aws/handlers/analytics.py index 4e5bbfa8aa085..b0cbada8b1e17 100644 --- a/localstack-core/localstack/aws/handlers/analytics.py +++ b/localstack-core/localstack/aws/handlers/analytics.py @@ -1,6 +1,5 @@ import logging import threading -from typing import Optional from localstack import config from localstack.aws.api import RequestContext @@ -52,7 +51,7 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo ) ) - def _get_err_type(self, context: RequestContext, response: Response) -> Optional[str]: + def _get_err_type(self, context: RequestContext, response: Response) -> str | None: """ Attempts to re-use the existing service_response, or parse and return the error type from the response body, e.g. ``ResourceInUseException``. diff --git a/localstack-core/localstack/aws/handlers/cors.py b/localstack-core/localstack/aws/handlers/cors.py index 13540e0165710..dc255abe36e6d 100644 --- a/localstack-core/localstack/aws/handlers/cors.py +++ b/localstack-core/localstack/aws/handlers/cors.py @@ -4,7 +4,6 @@ import logging import re -from typing import List, Set from urllib.parse import urlparse from werkzeug.datastructures import Headers @@ -83,7 +82,7 @@ ] -def _get_allowed_cors_internal_domains() -> Set[str]: +def _get_allowed_cors_internal_domains() -> set[str]: """ Construct the list of allowed internal domains for CORS enforcement purposes Defined as function to allow easier testing with monkeypatch of config values @@ -94,7 +93,7 @@ def _get_allowed_cors_internal_domains() -> Set[str]: _ALLOWED_INTERNAL_DOMAINS = _get_allowed_cors_internal_domains() -def _get_allowed_cors_ports() -> Set[int]: +def _get_allowed_cors_ports() -> set[int]: """ Construct the list of allowed ports for CORS enforcement purposes Defined as function to allow easier testing with monkeypatch of config values @@ -105,7 +104,7 @@ def _get_allowed_cors_ports() -> Set[int]: _ALLOWED_INTERNAL_PORTS = _get_allowed_cors_ports() -def _get_allowed_cors_origins() -> List[str]: +def _get_allowed_cors_origins() -> list[str]: """Construct the list of allowed origins for CORS enforcement purposes""" result = [ # allow access from Web app and localhost domains @@ -210,7 +209,7 @@ def is_cors_origin_allowed(headers: Headers) -> bool: return True @staticmethod - def _is_in_allowed_origins(allowed_origins: List[str], origin: str) -> bool: + def _is_in_allowed_origins(allowed_origins: list[str], origin: str) -> bool: """Returns true if the `origin` is in the `allowed_origins`.""" for allowed_origin in allowed_origins: if allowed_origin == "*" or origin == allowed_origin: diff --git a/localstack-core/localstack/aws/handlers/logging.py b/localstack-core/localstack/aws/handlers/logging.py index 2113b67fa5176..4904603325f19 100644 --- a/localstack-core/localstack/aws/handlers/logging.py +++ b/localstack-core/localstack/aws/handlers/logging.py @@ -2,7 +2,6 @@ import logging from functools import cached_property -from typing import Type from localstack.aws.api import RequestContext, ServiceException from localstack.aws.chain import ExceptionHandler, HandlerChain @@ -71,7 +70,7 @@ def internal_http_logger(self): ) # make sure loggers are loaded after logging config is loaded - def _prepare_logger(self, logger: logging.Logger, formatter: Type): + def _prepare_logger(self, logger: logging.Logger, formatter: type): if logger.isEnabledFor(logging.DEBUG): logger.propagate = False handler = create_default_handler(logger.level) diff --git a/localstack-core/localstack/aws/handlers/metric_handler.py b/localstack-core/localstack/aws/handlers/metric_handler.py index 6a1ad8f16b982..6bd16b2f43016 100644 --- a/localstack-core/localstack/aws/handlers/metric_handler.py +++ b/localstack-core/localstack/aws/handlers/metric_handler.py @@ -1,5 +1,4 @@ import logging -from typing import List, Optional from localstack import config from localstack.aws.api import RequestContext @@ -16,7 +15,7 @@ class MetricHandlerItem: request_id: str request_context: RequestContext - parameters_after_parse: Optional[List[str]] + parameters_after_parse: list[str] | None def __init__(self, request_contex: RequestContext) -> None: super().__init__() @@ -35,7 +34,7 @@ class Metric: headers: str parameters: str status_code: int - response_code: Optional[str] + response_code: str | None exception: str origin: str xfail: bool @@ -134,7 +133,7 @@ def __eq__(self, other): class MetricHandler: - metric_data: List[Metric] = [] + metric_data: list[Metric] = [] def __init__(self) -> None: self.metrics_handler_items = {} diff --git a/localstack-core/localstack/aws/handlers/service.py b/localstack-core/localstack/aws/handlers/service.py index edef0699c3539..295e7ffa3bb90 100644 --- a/localstack-core/localstack/aws/handlers/service.py +++ b/localstack-core/localstack/aws/handlers/service.py @@ -3,7 +3,7 @@ import logging import traceback from collections import defaultdict -from typing import Any, Dict, Union +from typing import Any from botocore.model import OperationModel, ServiceModel @@ -49,10 +49,10 @@ class ServiceRequestParser(Handler): already be resolved in the RequestContext (e.g., through a ServiceNameParser) """ - parsers: Dict[str, RequestParser] + parsers: dict[str, RequestParser] def __init__(self): - self.parsers = dict() + self.parsers = {} def __call__(self, chain: HandlerChain, context: RequestContext, response: Response): # determine service @@ -89,10 +89,10 @@ class ServiceRequestRouter(Handler): Routes ServiceOperations to Handlers. """ - handlers: Dict[ServiceOperation, Handler] + handlers: dict[ServiceOperation, Handler] def __init__(self): - self.handlers = dict() + self.handlers = {} def __call__(self, chain: HandlerChain, context: RequestContext, response: Response): if not context.service: @@ -118,7 +118,7 @@ def add_handler(self, key: ServiceOperation, handler: Handler): self.handlers[key] = handler - def add_provider(self, provider: Any, service: Union[str, ServiceModel]): + def add_provider(self, provider: Any, service: str | ServiceModel): self.add_skeleton(create_skeleton(service, provider)) def add_skeleton(self, skeleton: Skeleton): @@ -184,7 +184,7 @@ def create_exception_response(self, exception: Exception, context: RequestContex if not self.handle_internal_failures: return - if config.DEBUG: + if config.INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE: exception = "".join( traceback.format_exception( type(exception), value=exception, tb=exception.__traceback__ @@ -278,7 +278,7 @@ class ServiceResponseHandlers(Handler): are only called if the request context has a service, and there are handlers for that particular service. """ - handlers: Dict[str, CompositeResponseHandler] + handlers: dict[str, CompositeResponseHandler] def __init__(self): self.handlers = defaultdict(CompositeResponseHandler) diff --git a/localstack-core/localstack/aws/mocking.py b/localstack-core/localstack/aws/mocking.py index 2231b76eddbfb..7a6319709115d 100644 --- a/localstack-core/localstack/aws/mocking.py +++ b/localstack-core/localstack/aws/mocking.py @@ -4,7 +4,7 @@ import re from datetime import date, datetime from functools import lru_cache, singledispatch -from typing import Dict, List, Optional, Set, Tuple, Union, cast +from typing import cast import botocore import networkx @@ -32,18 +32,9 @@ "boolean", } -Instance = Union[ - Dict[str, "Instance"], - List["Instance"], - str, - bytes, - map, - list, - float, - int, - bool, - date, -] +Instance = ( + dict[str, "Instance"] | list["Instance"] | str | bytes | map | list | float | int | bool | date +) # https://github.com/boto/botocore/issues/2623 StringShape.METADATA_ATTRS.append("pattern") @@ -66,14 +57,14 @@ class ShapeGraph(networkx.DiGraph): - root: Union[ListShape, StructureShape, MapShape] - cycle: List[Tuple[str, str]] - cycle_shapes: List[str] + root: ListShape | StructureShape | MapShape + cycle: list[tuple[str, str]] + cycle_shapes: list[str] def populate_graph(graph: networkx.DiGraph, root: Shape): - stack: List[Shape] = [root] - visited: Set[str] = set() + stack: list[Shape] = [root] + visited: set[str] = set() while stack: cur = stack.pop() @@ -108,7 +99,7 @@ def shape_graph(root: Shape) -> ShapeGraph: graph.root = root populate_graph(graph, root) - cycles = list() + cycles = [] shapes = set() for node in graph.nodes: try: @@ -230,14 +221,14 @@ def sanitize_arn_pattern(pattern: str) -> str: @singledispatch -def generate_instance(shape: Shape, graph: ShapeGraph) -> Optional[Instance]: +def generate_instance(shape: Shape, graph: ShapeGraph) -> Instance | None: if shape is None: return None raise ValueError("could not generate shape for type %s" % shape.type_name) @generate_instance.register -def _(shape: StructureShape, graph: ShapeGraph) -> Dict[str, Instance]: +def _(shape: StructureShape, graph: ShapeGraph) -> dict[str, Instance]: if shape.is_tagged_union: k, v = random.choice(list(shape.members.items())) members = {k: v} @@ -255,14 +246,14 @@ def _(shape: StructureShape, graph: ShapeGraph) -> Dict[str, Instance]: @generate_instance.register -def _(shape: ListShape, graph: ShapeGraph) -> List[Instance]: +def _(shape: ListShape, graph: ShapeGraph) -> list[Instance]: if shape.name in graph.cycle_shapes: return [] return [generate_instance(shape.member, graph) for _ in range(shape.metadata.get("min", 1))] @generate_instance.register -def _(shape: MapShape, graph: ShapeGraph) -> Dict[str, Instance]: +def _(shape: MapShape, graph: ShapeGraph) -> dict[str, Instance]: if shape.name in graph.cycle_shapes: return {} return {generate_instance(shape.key, graph): generate_instance(shape.value, graph)} @@ -379,7 +370,7 @@ def _(shape: StringShape, graph: ShapeGraph) -> str: @generate_instance.register -def _(shape: Shape, graph: ShapeGraph) -> Union[int, float, bool, bytes, date]: +def _(shape: Shape, graph: ShapeGraph) -> int | float | bool | bytes | date: if shape.type_name in ["integer", "long"]: return shape.metadata.get("min", 1) if shape.type_name in ["float", "double"]: @@ -427,7 +418,7 @@ def create_mocking_dispatch_table(service) -> DispatchTable: return dispatch_table -@lru_cache() +@lru_cache def get_mocking_skeleton(service: str) -> Skeleton: service = load_service(service) return Skeleton(service, create_mocking_dispatch_table(service)) diff --git a/localstack-core/localstack/aws/protocol/op_router.py b/localstack-core/localstack/aws/protocol/op_router.py index f4c5f1019aa02..ce746341ae88d 100644 --- a/localstack-core/localstack/aws/protocol/op_router.py +++ b/localstack-core/localstack/aws/protocol/op_router.py @@ -1,5 +1,6 @@ from collections import defaultdict -from typing import Any, Dict, List, Mapping, NamedTuple, Optional, Tuple +from collections.abc import Mapping +from typing import Any, NamedTuple from urllib.parse import parse_qs, unquote from botocore.model import OperationModel, ServiceModel, StructureShape @@ -24,8 +25,8 @@ class _HttpOperation(NamedTuple): operation: OperationModel path: str method: str - query_args: Mapping[str, List[str]] - header_args: List[str] + query_args: Mapping[str, list[str]] + header_args: list[str] deprecated: bool @staticmethod @@ -49,11 +50,11 @@ def from_operation(op: OperationModel) -> "_HttpOperation": path_query = uri.split("?") path = path_query[0] header_args = [] - query_args: Dict[str, List[str]] = {} + query_args: dict[str, list[str]] = {} if len(path_query) > 1: # parse the query args of the request URI (they are mandatory) - query_args: Dict[str, List[str]] = parse_qs(path_query[1], keep_blank_values=True) + query_args: dict[str, list[str]] = parse_qs(path_query[1], keep_blank_values=True) # for mandatory keys without values, keep an empty list (instead of [''] - the result of parse_qs) query_args = {k: filter(None, v) for k, v in query_args.items()} @@ -85,8 +86,8 @@ class _RequiredArgsRule: """ endpoint: Any - required_query_args: Optional[Mapping[str, List[Any]]] - required_header_args: List[str] + required_query_args: Mapping[str, list[Any]] | None + required_header_args: list[str] match_score: int def __init__(self, operation: _HttpOperation) -> None: @@ -139,7 +140,7 @@ class _RequestMatchingRule(StrictMethodRule): """ def __init__( - self, string: str, operations: List[_HttpOperation], method: str, **kwargs + self, string: str, operations: list[_HttpOperation], method: str, **kwargs ) -> None: super().__init__(string=string, method=method, **kwargs) # Create a rule which checks all required arguments (not only the path and method) @@ -174,7 +175,7 @@ def _create_service_map(service: ServiceModel) -> Map: rules = [] # group all operations by their path and method - path_index: Dict[(str, str), List[_HttpOperation]] = defaultdict(list) + path_index: dict[(str, str), list[_HttpOperation]] = defaultdict(list) for op in ops: http_op = _HttpOperation.from_operation(op) path_index[(http_op.path, http_op.method)].append(http_op) @@ -216,7 +217,7 @@ class RestServiceOperationRouter: def __init__(self, service: ServiceModel): self._map = _create_service_map(service) - def match(self, request: Request) -> Tuple[OperationModel, Mapping[str, Any]]: + def match(self, request: Request) -> tuple[OperationModel, Mapping[str, Any]]: """ Matches the given request to the operation it targets (or raises an exception if no operation matches). diff --git a/localstack-core/localstack/aws/protocol/parser.py b/localstack-core/localstack/aws/protocol/parser.py index 96fd3d16cf0aa..1e93ba4322437 100644 --- a/localstack-core/localstack/aws/protocol/parser.py +++ b/localstack-core/localstack/aws/protocol/parser.py @@ -68,8 +68,9 @@ import functools import re from abc import ABC +from collections.abc import Mapping from email.utils import parsedate_to_datetime -from typing import IO, Any, Dict, List, Mapping, Optional, Tuple, Union +from typing import IO, Any from xml.etree import ElementTree as ETree import dateutil.parser @@ -107,7 +108,7 @@ def _get_text_content( self, request: Request, shape: Shape, - node_or_string: Union[ETree.Element, str], + node_or_string: ETree.Element | str, uri_params: Mapping[str, Any] = None, ): if hasattr(node_or_string, "text"): @@ -203,7 +204,7 @@ def __init__(self, service: ServiceModel) -> None: self.service = service @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: """ Determines which operation the request was aiming for and parses the incoming request such that the resulting dictionary can be used to invoke the service's function implementation. @@ -367,7 +368,7 @@ class QueryRequestParser(RequestParser): """ @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: instance = request.values if "Action" not in instance: raise ProtocolParserError( @@ -510,7 +511,7 @@ def _parse_list( # We collect the list value as well as the integer indicating the list position so we can # later sort the list by the position, in case they attribute values are unordered - result: List[Tuple[int, Any]] = [] + result: list[tuple[int, Any]] = [] i = 0 while True: @@ -559,7 +560,7 @@ def __init__(self, service: ServiceModel) -> None: self._operation_router = RestServiceOperationRouter(service) @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: try: operation, uri_params = self._operation_router.match(request) except NotFound as e: @@ -578,7 +579,7 @@ def _parse_payload( self, request: Request, shape: Shape, - member_shapes: Dict[str, Shape], + member_shapes: dict[str, Shape], uri_params: Mapping[str, Any], final_parsed: dict, ) -> None: @@ -781,7 +782,7 @@ def _parse_xml_string_to_dom(xml_string: str) -> ETree.Element: ) from e return root - def _build_name_to_xml_node(self, parent_node: Union[list, ETree.Element]) -> dict: + def _build_name_to_xml_node(self, parent_node: list | ETree.Element) -> dict: # If the parent node is actually a list. We should not be trying # to serialize it to a dictionary. Instead, return the first element # in the list. @@ -824,9 +825,9 @@ def _parse_structure( self, request: Request, shape: StructureShape, - value: Optional[dict], + value: dict | None, uri_params: Mapping[str, Any] = None, - ) -> Optional[dict]: + ) -> dict | None: if shape.is_document_type: final_parsed = value else: @@ -849,9 +850,9 @@ def _parse_map( self, request: Request, shape: MapShape, - value: Optional[dict], + value: dict | None, uri_params: Mapping[str, Any] = None, - ) -> Optional[dict]: + ) -> dict | None: if value is None: return None parsed = {} @@ -916,7 +917,7 @@ class JSONRequestParser(BaseJSONRequestParser): """ @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: target = request.headers["X-Amz-Target"] # assuming that the last part of the target string (e.g., "x.y.z.MyAction") contains the operation name operation_name = target.rpartition(".")[2] @@ -1038,9 +1039,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): ) @staticmethod - def _set_request_props( - request: Request, path: str, host: str, raw_uri: Optional[str] = None - ): + def _set_request_props(request: Request, path: str, host: str, raw_uri: str | None = None): """Sets the HTTP request's path and host and clears the cache in the request object.""" request.path = path request.headers["Host"] = host @@ -1072,7 +1071,7 @@ def _is_vhost_address_get_bucket(request: Request) -> str | None: return uses_host_addressing(request.headers) @_handle_exceptions - def parse(self, request: Request) -> Tuple[OperationModel, Any]: + def parse(self, request: Request) -> tuple[OperationModel, Any]: """Handle virtual-host-addressing for S3.""" with self.VirtualHostRewriter(request): return super().parse(request) diff --git a/localstack-core/localstack/aws/protocol/serializer.py b/localstack-core/localstack/aws/protocol/serializer.py index 86cabdd3487b6..5fb60d4319c5b 100644 --- a/localstack-core/localstack/aws/protocol/serializer.py +++ b/localstack-core/localstack/aws/protocol/serializer.py @@ -79,10 +79,11 @@ import string from abc import ABC from binascii import crc32 +from collections.abc import Iterable, Iterator from datetime import datetime from email.utils import formatdate from struct import pack -from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union +from typing import Any from xml.etree import ElementTree as ETree import xmltodict @@ -181,14 +182,14 @@ class ResponseSerializer(abc.ABC): AWS_BINARY_DATA_TYPE_STRING = 7 # Defines the supported mime types of the specific serializer. Sorted by priority (preferred / default first). # Needs to be specified by subclasses. - SUPPORTED_MIME_TYPES: List[str] = [] + SUPPORTED_MIME_TYPES: list[str] = [] @_handle_exceptions def serialize_to_response( self, response: dict, operation_model: OperationModel, - headers: Optional[Dict | Headers], + headers: dict | Headers | None, request_id: str, ) -> Response: """ @@ -234,7 +235,7 @@ def serialize_error_to_response( self, error: ServiceException, operation_model: OperationModel, - headers: Optional[Dict | Headers], + headers: dict | Headers | None, request_id: str, ) -> Response: """ @@ -274,7 +275,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -289,7 +290,7 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: """ Actually serializes the given params for the given shape to a string for the transmission in the body of the response. @@ -390,9 +391,9 @@ def event_stream_serializer() -> Iterable[bytes]: def _encode_event_payload( self, event_type: str, - content: Union[str, bytes] = "", - error_code: Optional[str] = None, - error_message: Optional[str] = None, + content: str | bytes = "", + error_code: str | None = None, + error_message: str | None = None, ) -> bytes: """ Encodes the given event payload according to AWS specific binary event encoding. @@ -469,7 +470,7 @@ def _create_default_response(self, operation_model: OperationModel, mime_type: s """ return Response(status=operation_model.http.get("responseCode", 200)) - def _get_mime_type(self, headers: Optional[Dict | Headers]) -> str: + def _get_mime_type(self, headers: dict | Headers | None) -> str: """ Extracts the accepted mime type from the request headers and returns a matching, supported mime type for the serializer or the default mime type of the service if there is no match. @@ -521,9 +522,7 @@ def _timestamp_rfc822(self, value: datetime) -> str: value = self._timestamp_unixtimestamp(value) return formatdate(value, usegmt=True) - def _convert_timestamp_to_str( - self, value: Union[int, str, datetime], timestamp_format=None - ) -> str: + def _convert_timestamp_to_str(self, value: int | str | datetime, timestamp_format=None) -> str: if timestamp_format is None: timestamp_format = self.TIMESTAMP_FORMAT timestamp_format = timestamp_format.lower() @@ -540,7 +539,7 @@ def _get_serialized_name(shape: Shape, default_name: str) -> str: """ return shape.serialization.get("name", default_name) - def _get_base64(self, value: Union[str, bytes]): + def _get_base64(self, value: str | bytes): """ Returns the base64-encoded version of value, handling both strings and bytes. The returned value is a string @@ -550,7 +549,7 @@ def _get_base64(self, value: Union[str, bytes]): value = value.encode(self.DEFAULT_ENCODING) return base64.b64encode(value).strip().decode(self.DEFAULT_ENCODING) - def _encode_payload(self, body: Union[bytes, str]) -> bytes: + def _encode_payload(self, body: bytes | str) -> bytes: if isinstance(body, str): return body.encode(self.DEFAULT_ENCODING) return body @@ -578,7 +577,7 @@ def _add_md5_header(self, response: Response): md5_digest = calculate_md5(body) headers["Content-MD5"] = md5_digest - def _get_error_message(self, error: Exception) -> Optional[str]: + def _get_error_message(self, error: Exception) -> str | None: return str(error) if error is not None and str(error) != "None" else None @@ -661,14 +660,14 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: root = self._serialize_body_params_to_xml(params, shape, operation_model, mime_type) self._prepare_additional_traits_in_xml(root, request_id) return self._node_to_string(root, mime_type) def _serialize_body_params_to_xml( self, params: dict, shape: Shape, operation_model: OperationModel, mime_type: str - ) -> Optional[ETree.Element]: + ) -> ETree.Element | None: if shape is None: return # The botocore serializer expects `shape.serialization["name"]`, but this isn't always present for responses @@ -812,7 +811,7 @@ def _serialize_type_boolean(xmlnode: ETree.Element, params: bool, _, name: str, node.text = str_value def _serialize_type_blob( - self, xmlnode: ETree.Element, params: Union[str, bytes], _, name: str, __ + self, xmlnode: ETree.Element, params: str | bytes, _, name: str, __ ) -> None: node = ETree.SubElement(xmlnode, name) node.text = self._get_base64(params) @@ -838,7 +837,7 @@ def _default_serialize(self, xmlnode: ETree.Element, params: str, _, name: str, node = ETree.SubElement(xmlnode, name) node.text = str(params) - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): """ Prepares the XML root node before being serialized with additional traits (like the Response ID in the Query protocol). @@ -851,7 +850,7 @@ def _create_default_response(self, operation_model: OperationModel, mime_type: s response.headers["Content-Type"] = mime_type return response - def _node_to_string(self, root: Optional[ETree.Element], mime_type: str) -> Optional[str]: + def _node_to_string(self, root: ETree.Element | None, mime_type: str) -> str | None: """Generates the string representation of the given XML element.""" if root is not None: content = ETree.tostring( @@ -877,7 +876,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -904,7 +903,7 @@ def _serialize_payload( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -975,7 +974,7 @@ def _serialize_content_type( """ pass - def _has_streaming_payload(self, payload: Optional[str], shape_members): + def _has_streaming_payload(self, payload: str | None, shape_members): """Determine if payload is streaming (a blob or string).""" return payload is not None and shape_members[payload].type_name in ["blob", "string"] @@ -1040,7 +1039,7 @@ def _serialize_header_value(self, shape: Shape, value: Any): else: return value - def _partition_members(self, parameters: dict, shape: Optional[Shape]) -> Tuple[dict, dict]: + def _partition_members(self, parameters: dict, shape: Shape | None) -> tuple[dict, dict]: """Separates the top-level keys in the given parameters dict into header- and payload-located params.""" if not isinstance(shape, StructureShape): # If the shape isn't a structure, we default to the whole response being parsed in the body. @@ -1083,7 +1082,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -1130,7 +1129,7 @@ def _serialize_body_params_to_xml( root.append(node) return root - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): # Add the response metadata here (it's not defined in the specs) # For the ec2 and the query protocol, the root cannot be None at this time. response_metadata = ETree.SubElement(root, "ResponseMetadata") @@ -1179,7 +1178,7 @@ def _serialize_error( request_id_element.text = request_id response.set_response(self._encode_payload(self._node_to_string(root, mime_type))) - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): # The EC2 protocol does not use the root output shape, therefore we need to remove the hierarchy level # below the root level if len(root) > 0: @@ -1216,7 +1215,7 @@ def _serialize_error( mime_type: str, request_id: str, ) -> None: - body = dict() + body = {} # TODO implement different service-specific serializer configurations # - currently we set both, the `__type` member as well as the `X-Amzn-Errortype` header @@ -1251,7 +1250,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -1274,7 +1273,7 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: body = {} if shape is not None: self._serialize(body, params, shape, None, mime_type) @@ -1284,7 +1283,7 @@ def _serialize_body_params( else: return json.dumps(body) - def _serialize(self, body: dict, value: Any, shape, key: Optional[str], mime_type: str): + def _serialize(self, body: dict, value: Any, shape, key: str | None, mime_type: str): """This method dynamically invokes the correct `_serialize_type_*` method for each shape type.""" try: method = getattr(self, "_serialize_type_%s" % shape.type_name, self._default_serialize) @@ -1295,7 +1294,7 @@ def _serialize(self, body: dict, value: Any, shape, key: Optional[str], mime_typ ) from e def _serialize_type_structure( - self, body: dict, value: dict, shape: StructureShape, key: Optional[str], mime_type: str + self, body: dict, value: dict, shape: StructureShape, key: str | None, mime_type: str ): if value is None: return @@ -1369,9 +1368,7 @@ def _serialize_type_timestamp( timestamp_format = shape.serialization.get("timestampFormat") body[key] = self._convert_timestamp_to_str(value, timestamp_format) - def _serialize_type_blob( - self, body: dict, value: Union[str, bytes], _, key: str, mime_type: str - ): + def _serialize_type_blob(self, body: dict, value: str | bytes, _, key: str, mime_type: str): if mime_type in self.CBOR_TYPES: body[key] = value else: @@ -1466,7 +1463,7 @@ def _serialize_response( self, parameters: dict, response: Response, - shape: Optional[Shape], + shape: Shape | None, shape_members: dict, operation_model: OperationModel, mime_type: str, @@ -1528,7 +1525,7 @@ def _serialize_body_params( operation_model: OperationModel, mime_type: str, request_id: str, - ) -> Optional[str]: + ) -> str | None: root = self._serialize_body_params_to_xml(params, shape, operation_model, mime_type) # S3 does not follow the specs on the root tag name for 41 of 44 operations root.tag = self._RESPONSE_ROOT_TAGS.get(root.tag, root.tag) @@ -1568,7 +1565,7 @@ def _add_error_tags( def _create_empty_node(xmlnode: ETree.Element, name: str) -> None: ETree.SubElement(xmlnode, name) - def _prepare_additional_traits_in_xml(self, root: Optional[ETree.Element], request_id: str): + def _prepare_additional_traits_in_xml(self, root: ETree.Element | None, request_id: str): # some tools (Serverless) require a newline after the "\n" preamble line, e.g., for LocationConstraint if root and not root.tail: root.tail = "\n" @@ -1639,7 +1636,7 @@ def _default_serialize(self, xmlnode: ETree.Element, params: str, _, name: str, .replace("\r", "__marker__-r__marker__") ) - def _node_to_string(self, root: Optional[ETree.ElementTree], mime_type: str) -> Optional[str]: + def _node_to_string(self, root: ETree.ElementTree | None, mime_type: str) -> str | None: """Replaces the previously "marked" characters with their encoded value.""" generated_string = super()._node_to_string(root, mime_type) if generated_string is None: @@ -1785,7 +1782,7 @@ def create_serializer(service: ServiceModel) -> ResponseSerializer: def aws_response_serializer( - service_name: str, operation: str, protocol: Optional[ProtocolName] = None + service_name: str, operation: str, protocol: ProtocolName | None = None ): """ A decorator for an HTTP route that can serialize return values or exceptions into AWS responses. diff --git a/localstack-core/localstack/aws/protocol/service_router.py b/localstack-core/localstack/aws/protocol/service_router.py index 44d70efaac4df..5b3768fa882d6 100644 --- a/localstack-core/localstack/aws/protocol/service_router.py +++ b/localstack-core/localstack/aws/protocol/service_router.py @@ -1,5 +1,5 @@ import logging -from typing import NamedTuple, Optional, Set +from typing import NamedTuple from botocore.model import ServiceModel from werkzeug.exceptions import RequestEntityTooLarge @@ -28,15 +28,15 @@ class _ServiceIndicators(NamedTuple): # AWS service's "signing name" - Contained in the Authorization header # (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-auth-using-authorization-header.html) - signing_name: Optional[str] = None + signing_name: str | None = None # Target prefix as defined in the service specs for non-rest protocols - Contained in the X-Amz-Target header - target_prefix: Optional[str] = None + target_prefix: str | None = None # Targeted operation as defined in the service specs for non-rest protocols - Contained in the X-Amz-Target header - operation: Optional[str] = None + operation: str | None = None # Host field of the HTTP request - host: Optional[str] = None + host: str | None = None # Path of the HTTP request - path: Optional[str] = None + path: str | None = None def _extract_service_indicators(request: Request) -> _ServiceIndicators: @@ -111,7 +111,7 @@ def _extract_service_indicators(request: Request) -> _ServiceIndicators: } -def custom_signing_name_rules(signing_name: str, path: str) -> Optional[ServiceModelIdentifier]: +def custom_signing_name_rules(signing_name: str, path: str) -> ServiceModelIdentifier | None: """ Rules which are based on the signing name (in the auth header) and the request path. """ @@ -134,7 +134,7 @@ def custom_signing_name_rules(signing_name: str, path: str) -> Optional[ServiceM return rules.get("*", ServiceModelIdentifier(signing_name)) -def custom_host_addressing_rules(host: str) -> Optional[ServiceModelIdentifier]: +def custom_host_addressing_rules(host: str) -> ServiceModelIdentifier | None: """ Rules based on the host header of the request, which is typically the data plane of a service. @@ -147,7 +147,7 @@ def custom_host_addressing_rules(host: str) -> Optional[ServiceModelIdentifier]: return ServiceModelIdentifier("s3") -def custom_path_addressing_rules(path: str) -> Optional[ServiceModelIdentifier]: +def custom_path_addressing_rules(path: str) -> ServiceModelIdentifier | None: """ Rules which are only based on the request path. """ @@ -159,7 +159,7 @@ def custom_path_addressing_rules(path: str) -> Optional[ServiceModelIdentifier]: return ServiceModelIdentifier("lambda") -def legacy_s3_rules(request: Request) -> Optional[ServiceModelIdentifier]: +def legacy_s3_rules(request: Request) -> ServiceModelIdentifier | None: """ *Legacy* rules which allow us to fallback to S3 if no other service was matched. All rules which are implemented here should be removed once we make sure it would not break any use-cases. @@ -228,7 +228,7 @@ def legacy_s3_rules(request: Request) -> Optional[ServiceModelIdentifier]: def resolve_conflicts( - candidates: Set[ServiceModelIdentifier], request: Request + candidates: set[ServiceModelIdentifier], request: Request ) -> ServiceModelIdentifier: """ Some service definitions are overlapping to a point where they are _not_ distinguishable at all @@ -258,7 +258,7 @@ def resolve_conflicts( def determine_aws_service_model_for_data_plane( request: Request, services: ServiceCatalog = None -) -> Optional[ServiceModel]: +) -> ServiceModel | None: """ A stripped down version of ``determine_aws_service_model`` which only checks hostname indicators for the AWS data plane, such as s3 websites, lambda function URLs, or API gateway routes. @@ -271,7 +271,7 @@ def determine_aws_service_model_for_data_plane( def determine_aws_service_model( request: Request, services: ServiceCatalog = None -) -> Optional[ServiceModel]: +) -> ServiceModel | None: """ Tries to determine the name of the AWS service an incoming request is targeting. :param request: to determine the target service name of diff --git a/localstack-core/localstack/aws/protocol/validate.py b/localstack-core/localstack/aws/protocol/validate.py index 30d1be4355fb0..9be5d6b55b4c5 100644 --- a/localstack-core/localstack/aws/protocol/validate.py +++ b/localstack-core/localstack/aws/protocol/validate.py @@ -1,6 +1,6 @@ """Slightly extends the ``botocore.validate`` package to provide better integration with our parser/serializer.""" -from typing import Any, Dict, List, NamedTuple +from typing import Any, NamedTuple from botocore.model import OperationModel, Shape from botocore.validate import ParamValidator as BotocoreParamValidator @@ -22,7 +22,7 @@ class Error(NamedTuple): reason: str name: str - attributes: Dict[str, Any] + attributes: dict[str, Any] class ParameterValidationError(Exception): @@ -88,11 +88,11 @@ class EmptyInput(ParameterValidationError): class ValidationErrors(BotocoreValidationErrors): - def __init__(self, shape: Shape, params: Dict[str, Any]): + def __init__(self, shape: Shape, params: dict[str, Any]): super().__init__() self.shape = shape self.params = params - self._exceptions: List[ParameterValidationError] = [] + self._exceptions: list[ParameterValidationError] = [] @property def exceptions(self): @@ -133,7 +133,7 @@ def to_exception(self, error: Error) -> ParameterValidationError: class ParamValidator(BotocoreParamValidator): - def validate(self, params: Dict[str, Any], shape: Shape): + def validate(self, params: dict[str, Any], shape: Shape): """Validate parameters against a shape model. This method will validate the parameters against a provided shape model. diff --git a/localstack-core/localstack/aws/scaffold.py b/localstack-core/localstack/aws/scaffold.py index 3d9c0e3e55db4..f1018d4f635ce 100644 --- a/localstack-core/localstack/aws/scaffold.py +++ b/localstack-core/localstack/aws/scaffold.py @@ -1,10 +1,10 @@ import io import keyword import re +from collections import OrderedDict from functools import cached_property from multiprocessing import Pool from pathlib import Path -from typing import Dict, List, Optional, Set import click from botocore import xform_name @@ -18,7 +18,6 @@ StringShape, StructureShape, ) -from typing_extensions import OrderedDict from localstack.aws.spec import load_service from localstack.utils.common import camel_to_snake_case, snake_to_camel_case @@ -75,7 +74,7 @@ def __init__(self, service: ServiceModel, shape: Shape) -> None: self.shape = shape @cached_property - def request_operation(self) -> Optional[OperationModel]: + def request_operation(self) -> OperationModel | None: for operation_name in self.service.operation_names: operation = self.service.operation_model(operation_name) if operation.input_shape is None: @@ -89,7 +88,7 @@ def request_operation(self) -> Optional[OperationModel]: return None @cached_property - def response_operation(self) -> Optional[OperationModel]: + def response_operation(self) -> OperationModel | None: for operation_name in self.service.operation_names: operation = self.service.operation_model(operation_name) if operation.output_shape is None: @@ -128,7 +127,7 @@ def is_enum(self): return isinstance(self.shape, StringShape) and self.shape.enum @property - def dependencies(self) -> List[str]: + def dependencies(self) -> list[str]: shape = self.shape if isinstance(shape, StructureShape): @@ -327,7 +326,7 @@ def generate_service_types(output, service: ServiceModel, doc=True): output.write("\n") # ==================================== print type declarations - nodes: Dict[str, ShapeNode] = {} + nodes: dict[str, ShapeNode] = {} for shape_name in service.shape_names: shape = service.shape_for(shape_name) @@ -338,9 +337,9 @@ def generate_service_types(output, service: ServiceModel, doc=True): # output.write(f' "{name}",\n') # output.write("]\n") - printed: Set[str] = set() - visited: Set[str] = set() - stack: List[str] = list(nodes.keys()) + printed: set[str] = set() + visited: set[str] = set() + stack: list[str] = list(nodes.keys()) stack = sorted(stack, key=lambda name: nodes[name].get_order()) stack.reverse() diff --git a/localstack-core/localstack/aws/serving/edge.py b/localstack-core/localstack/aws/serving/edge.py index 0e204a4d96f88..be59bcef1bc58 100644 --- a/localstack-core/localstack/aws/serving/edge.py +++ b/localstack-core/localstack/aws/serving/edge.py @@ -1,6 +1,5 @@ import logging import threading -from typing import List from rolo.gateway.wsgi import WsgiGateway @@ -15,7 +14,7 @@ def serve_gateway( - listen: HostAndPort | List[HostAndPort], use_ssl: bool, asynchronous: bool = False + listen: HostAndPort | list[HostAndPort], use_ssl: bool, asynchronous: bool = False ): """ Implementation of the edge.do_start_edge_proxy interface to start a Hypercorn server instance serving the @@ -37,7 +36,7 @@ def serve_gateway( def _serve_werkzeug( - gateway: LocalstackAwsGateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool + gateway: LocalstackAwsGateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool ): from werkzeug.serving import ThreadedWSGIServer @@ -57,7 +56,7 @@ def _serve_werkzeug( params["ssl_context"] = (cert_file_name, key_file_name) threads = [] - servers: List[ThreadedWSGIServer] = [] + servers: list[ThreadedWSGIServer] = [] for host_port in listen: kwargs = dict(params) @@ -90,7 +89,7 @@ def _shutdown_servers(): def _serve_hypercorn( - gateway: LocalstackAwsGateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool + gateway: LocalstackAwsGateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool ): from localstack.http.hypercorn import GatewayServer @@ -112,7 +111,7 @@ def _shutdown_gateway(): def _serve_twisted( - gateway: LocalstackAwsGateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool + gateway: LocalstackAwsGateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool ): from .twisted import serve_gateway diff --git a/localstack-core/localstack/aws/serving/hypercorn.py b/localstack-core/localstack/aws/serving/hypercorn.py index 450d2664badc9..def66e6ea9a00 100644 --- a/localstack-core/localstack/aws/serving/hypercorn.py +++ b/localstack-core/localstack/aws/serving/hypercorn.py @@ -1,5 +1,5 @@ import asyncio -from typing import Any, Optional, Tuple +from typing import Any from hypercorn import Config from hypercorn.asyncio import serve as serve_hypercorn @@ -15,7 +15,7 @@ def serve( host: str = "localhost", port: int = constants.DEFAULT_PORT_EDGE, use_reloader: bool = True, - ssl_creds: Optional[Tuple[Any, Any]] = None, + ssl_creds: tuple[Any, Any] | None = None, **kwargs, ) -> None: """ diff --git a/localstack-core/localstack/aws/serving/twisted.py b/localstack-core/localstack/aws/serving/twisted.py index 549150a73ae61..3ba0341387674 100644 --- a/localstack-core/localstack/aws/serving/twisted.py +++ b/localstack-core/localstack/aws/serving/twisted.py @@ -4,7 +4,6 @@ import logging import time -from typing import List from rolo.gateway import Gateway from rolo.serving.twisted import TwistedGateway @@ -130,7 +129,7 @@ def stop_thread_pool(self: ThreadPool, stop, timeout: float = None): def serve_gateway( - gateway: Gateway, listen: List[HostAndPort], use_ssl: bool, asynchronous: bool = False + gateway: Gateway, listen: list[HostAndPort], use_ssl: bool, asynchronous: bool = False ): """ Serve a Gateway instance using twisted. diff --git a/localstack-core/localstack/aws/serving/werkzeug.py b/localstack-core/localstack/aws/serving/werkzeug.py index 22e351adc4842..00e378b043a9b 100644 --- a/localstack-core/localstack/aws/serving/werkzeug.py +++ b/localstack-core/localstack/aws/serving/werkzeug.py @@ -1,5 +1,5 @@ import ssl -from typing import TYPE_CHECKING, Any, Optional, Tuple +from typing import TYPE_CHECKING, Any from rolo.gateway import Gateway from rolo.gateway.wsgi import WsgiGateway @@ -17,7 +17,7 @@ def serve( host: str = "localhost", port: int = constants.DEFAULT_PORT_EDGE, use_reloader: bool = True, - ssl_creds: Optional[Tuple[Any, Any]] = None, + ssl_creds: tuple[Any, Any] | None = None, **kwargs, ) -> None: """ diff --git a/localstack-core/localstack/aws/skeleton.py b/localstack-core/localstack/aws/skeleton.py index 9d66fa4b375c1..e72c87cea77e8 100644 --- a/localstack-core/localstack/aws/skeleton.py +++ b/localstack-core/localstack/aws/skeleton.py @@ -1,6 +1,7 @@ import inspect import logging -from typing import Any, Callable, Dict, NamedTuple, Optional, Union +from collections.abc import Callable +from typing import Any, NamedTuple from botocore import xform_name from botocore.model import ServiceModel @@ -20,10 +21,10 @@ LOG = logging.getLogger(__name__) -DispatchTable = Dict[str, ServiceRequestHandler] +DispatchTable = dict[str, ServiceRequestHandler] -def create_skeleton(service: Union[str, ServiceModel], delegate: Any): +def create_skeleton(service: str | ServiceModel, delegate: Any): if isinstance(service, str): service = load_service(service) @@ -49,10 +50,10 @@ def create_dispatch_table(delegate: object) -> DispatchTable: # scan class tree for @handler wrapped functions (reverse class tree so that inherited functions overwrite parent # functions) cls_tree = inspect.getmro(delegate.__class__) - handlers: Dict[str, HandlerAttributes] = {} + handlers: dict[str, HandlerAttributes] = {} cls_tree = reversed(list(cls_tree)) for cls in cls_tree: - if cls == object: + if cls is object: continue for name, fn in inspect.getmembers(cls, inspect.isfunction): @@ -98,9 +99,7 @@ def __init__( self.pass_context = pass_context self.expand_parameters = expand_parameters - def __call__( - self, context: RequestContext, request: ServiceRequest - ) -> Optional[ServiceResponse]: + def __call__(self, context: RequestContext, request: ServiceRequest) -> ServiceResponse | None: args = [] kwargs = {} @@ -122,7 +121,7 @@ class Skeleton: service: ServiceModel dispatch_table: DispatchTable - def __init__(self, service: ServiceModel, implementation: Union[Any, DispatchTable]): + def __init__(self, service: ServiceModel, implementation: Any | DispatchTable): self.service = service if isinstance(implementation, dict): diff --git a/localstack-core/localstack/aws/spec.py b/localstack-core/localstack/aws/spec.py index 1410ddde3e246..d28834bfb0e0e 100644 --- a/localstack-core/localstack/aws/spec.py +++ b/localstack-core/localstack/aws/spec.py @@ -4,8 +4,9 @@ import os import sys from collections import defaultdict +from collections.abc import Generator from functools import cached_property, lru_cache -from typing import Dict, Generator, List, Literal, NamedTuple, Optional, Tuple +from typing import Literal, NamedTuple import botocore import jsonpatch @@ -31,13 +32,13 @@ class ServiceModelIdentifier(NamedTuple): """ name: ServiceName - protocol: Optional[ProtocolName] = None + protocol: ProtocolName | None = None spec_patches_json = os.path.join(os.path.dirname(__file__), "spec-patches.json") -def load_spec_patches() -> Dict[str, list]: +def load_spec_patches() -> dict[str, list]: if not os.path.exists(spec_patches_json): return {} with open(spec_patches_json) as fd: @@ -59,9 +60,9 @@ class PatchingLoader(Loader): A custom botocore Loader that applies JSON patches from the given json patch file to the specs as they are loaded. """ - patches: Dict[str, list] + patches: dict[str, list] - def __init__(self, patches: Dict[str, list], *args, **kwargs): + def __init__(self, patches: dict[str, list], *args, **kwargs): # add the builtin data path to the extra_search_paths to ensure they are discovered by the loader super().__init__(*args, **kwargs) self.patches = patches @@ -94,12 +95,12 @@ class UnknownServiceProtocolError(UnknownServiceError): fmt = "Unknown service protocol: '{service_name}-{protocol}'." -def list_services() -> List[ServiceModel]: +def list_services() -> list[ServiceModel]: return [load_service(service) for service in loader.list_available_services("service-2")] def load_service( - service: ServiceName, version: Optional[str] = None, protocol: Optional[ProtocolName] = None + service: ServiceName, version: str | None = None, protocol: ProtocolName | None = None ) -> ServiceModel: """ Loads a service @@ -136,7 +137,7 @@ def load_service( return ServiceModel(service_description, service) -def iterate_service_operations() -> Generator[Tuple[ServiceModel, OperationModel], None, None]: +def iterate_service_operations() -> Generator[tuple[ServiceModel, OperationModel], None, None]: """ Returns one record per operation in the AWS service spec, where the first item is the service model the operation belongs to, and the second is the operation model. @@ -154,11 +155,11 @@ class ServiceCatalogIndex: The ServiceCatalogIndex enables fast lookups for common operations to determine a service from service indicators. """ - service_names: List[ServiceName] - target_prefix_index: Dict[str, List[ServiceModelIdentifier]] - signing_name_index: Dict[str, List[ServiceModelIdentifier]] - operations_index: Dict[str, List[ServiceModelIdentifier]] - endpoint_prefix_index: Dict[str, List[ServiceModelIdentifier]] + service_names: list[ServiceName] + target_prefix_index: dict[str, list[ServiceModelIdentifier]] + signing_name_index: dict[str, list[ServiceModelIdentifier]] + operations_index: dict[str, list[ServiceModelIdentifier]] + endpoint_prefix_index: dict[str, list[ServiceModelIdentifier]] class LazyServiceCatalogIndex: @@ -167,11 +168,11 @@ class LazyServiceCatalogIndex: """ @cached_property - def service_names(self) -> List[ServiceName]: + def service_names(self) -> list[ServiceName]: return list(self._services.keys()) @cached_property - def target_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def target_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: @@ -183,7 +184,7 @@ def target_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: return dict(result) @cached_property - def signing_name_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def signing_name_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: @@ -193,7 +194,7 @@ def signing_name_index(self) -> Dict[str, List[ServiceModelIdentifier]]: return dict(result) @cached_property - def operations_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def operations_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: @@ -208,7 +209,7 @@ def operations_index(self) -> Dict[str, List[ServiceModelIdentifier]]: return dict(result) @cached_property - def endpoint_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def endpoint_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: result = defaultdict(list) for service_models in self._services.values(): for service_model in service_models: @@ -218,7 +219,7 @@ def endpoint_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: return dict(result) @cached_property - def _services(self) -> Dict[ServiceName, List[ServiceModel]]: + def _services(self) -> dict[ServiceName, list[ServiceModel]]: services = defaultdict(list) for service in list_services(): services[service.service_name].append(service) @@ -232,38 +233,36 @@ def __init__(self, index: ServiceCatalogIndex = None): self.index = index or LazyServiceCatalogIndex() @lru_cache(maxsize=512) - def get( - self, name: ServiceName, protocol: Optional[ProtocolName] = None - ) -> Optional[ServiceModel]: + def get(self, name: ServiceName, protocol: ProtocolName | None = None) -> ServiceModel | None: return load_service(name, protocol=protocol) @property - def service_names(self) -> List[ServiceName]: + def service_names(self) -> list[ServiceName]: return self.index.service_names @property - def target_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def target_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.target_prefix_index @property - def signing_name_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def signing_name_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.signing_name_index @property - def operations_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def operations_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.operations_index @property - def endpoint_prefix_index(self) -> Dict[str, List[ServiceModelIdentifier]]: + def endpoint_prefix_index(self) -> dict[str, list[ServiceModelIdentifier]]: return self.index.endpoint_prefix_index - def by_target_prefix(self, target_prefix: str) -> List[ServiceModelIdentifier]: + def by_target_prefix(self, target_prefix: str) -> list[ServiceModelIdentifier]: return self.target_prefix_index.get(target_prefix, []) - def by_signing_name(self, signing_name: str) -> List[ServiceModelIdentifier]: + def by_signing_name(self, signing_name: str) -> list[ServiceModelIdentifier]: return self.signing_name_index.get(signing_name, []) - def by_operation(self, operation_name: str) -> List[ServiceModelIdentifier]: + def by_operation(self, operation_name: str) -> list[ServiceModelIdentifier]: return self.operations_index.get(operation_name, []) diff --git a/localstack-core/localstack/cli/localstack.py b/localstack-core/localstack/cli/localstack.py index 5cf31d8897b17..cdde905e702be 100644 --- a/localstack-core/localstack/cli/localstack.py +++ b/localstack-core/localstack/cli/localstack.py @@ -3,7 +3,7 @@ import os import sys import traceback -from typing import Dict, List, Optional, Tuple, TypedDict +from typing import Optional, TypedDict import click import requests @@ -405,7 +405,7 @@ def cmd_status_services(format_: str) -> None: raise CLIError(f"could not connect to LocalStack health endpoint at {url}") -def _print_service_table(services: Dict[str, str]) -> None: +def _print_service_table(services: dict[str, str]) -> None: from rich.table import Table status_display = { @@ -486,9 +486,9 @@ def cmd_start( no_banner: bool, detached: bool, network: str = None, - env: Tuple = (), - publish: Tuple = (), - volume: Tuple = (), + env: tuple = (), + publish: tuple = (), + volume: tuple = (), host_dns: bool = False, stack: str = None, ) -> None: @@ -825,7 +825,7 @@ def cmd_update_docker_images() -> None: update_images(localstack_images) -def update_images(image_list: List[str]) -> None: +def update_images(image_list: list[str]) -> None: from rich.markup import escape from rich.progress import MofNCompleteColumn, Progress diff --git a/localstack-core/localstack/cli/lpm.py b/localstack-core/localstack/cli/lpm.py index ad4a6f5489d5c..719051b8fe38e 100644 --- a/localstack-core/localstack/cli/lpm.py +++ b/localstack-core/localstack/cli/lpm.py @@ -1,7 +1,7 @@ import itertools import logging from multiprocessing.pool import ThreadPool -from typing import List, Optional +from typing import Optional import click from rich.console import Console @@ -74,7 +74,7 @@ def _do_install_package(package: Package, version: str = None, target: InstallTa help="target of the installation", ) def install( - package: List[str], + package: list[str], parallel: Optional[int] = 1, version: Optional[str] = None, target: Optional[str] = None, diff --git a/localstack-core/localstack/config.py b/localstack-core/localstack/config.py index 247e9ae26482f..f9022f2231d6e 100644 --- a/localstack-core/localstack/config.py +++ b/localstack-core/localstack/config.py @@ -9,7 +9,8 @@ import time import warnings from collections import defaultdict -from typing import Any, Dict, List, Mapping, Optional, Tuple, TypeVar, Union +from collections.abc import Mapping +from typing import Any, Optional, TypeVar, Union from localstack import constants from localstack.constants import ( @@ -233,7 +234,7 @@ def is_env_not_false(env_var_name: str) -> bool: return os.environ.get(env_var_name, "").lower().strip() not in FALSE_STRINGS -def load_environment(profiles: str = None, env=os.environ) -> List[str]: +def load_environment(profiles: str = None, env=os.environ) -> list[str]: """Loads the environment variables from ~/.localstack/{profile}.env, for each profile listed in the profiles. :param env: environment to load profile to. Defaults to `os.environ` :param profiles: a comma separated list of profiles to load (defaults to "default") @@ -337,7 +338,7 @@ def in_docker(): return False except Exception: pass - with open("/proc/1/cgroup", "rt") as ifh: + with open("/proc/1/cgroup") as ifh: content = ifh.read() if "docker" in content or "buildkit" in content: return True @@ -348,7 +349,7 @@ def in_docker(): # containerd does not set any specific file or config, but it does use # io.containerd.snapshotter.v1.overlayfs as the overlay filesystem for `/`. try: - with open("/proc/mounts", "rt") as infile: + with open("/proc/mounts") as infile: for line in infile: line = line.strip() @@ -603,9 +604,7 @@ def _validate_port(cls, port_s: str) -> int: def _get_unprivileged_port_range_start(self) -> int: try: - with open( - "/proc/sys/net/ipv4/ip_unprivileged_port_start", "rt" - ) as unprivileged_port_start: + with open("/proc/sys/net/ipv4/ip_unprivileged_port_start") as unprivileged_port_start: port = unprivileged_port_start.read() return int(port.strip()) except Exception: @@ -637,7 +636,7 @@ def __repr__(self) -> str: return f"HostAndPort(host={self.host}, port={self.port})" -class UniqueHostAndPortList(List[HostAndPort]): +class UniqueHostAndPortList(list[HostAndPort]): """ Container type that ensures that ports added to the list are unique based on these rules: @@ -650,7 +649,7 @@ class UniqueHostAndPortList(List[HostAndPort]): - Identical identical hosts and ports are de-duped """ - def __init__(self, iterable: Union[List[HostAndPort], None] = None): + def __init__(self, iterable: Union[list[HostAndPort], None] = None): super().__init__(iterable or []) self._ensure_unique() @@ -661,10 +660,10 @@ def _ensure_unique(self): if len(self) <= 1: return - unique: List[HostAndPort] = list() + unique: list[HostAndPort] = [] # Build a dictionary of hosts by port - hosts_by_port: Dict[int, List[str]] = defaultdict(list) + hosts_by_port: dict[int, list[str]] = defaultdict(list) for item in self: hosts_by_port[item.port].append(item.host) @@ -696,7 +695,7 @@ def append(self, value: HostAndPort): def populate_edge_configuration( environment: Mapping[str, str], -) -> Tuple[HostAndPort, UniqueHostAndPortList]: +) -> tuple[HostAndPort, UniqueHostAndPortList]: """Populate the LocalStack edge configuration from environment variables.""" localstack_host_raw = environment.get("LOCALSTACK_HOST") gateway_listen_raw = environment.get("GATEWAY_LISTEN") @@ -824,6 +823,9 @@ def populate_edge_configuration( # Flag to enable the validation of the requests made to the LocalStack internal endpoints. Active by default. OPENAPI_VALIDATE_REQUEST = is_env_true("OPENAPI_VALIDATE_REQUEST") +# environment variable to determine whether to include stack traces in http responses +INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE = is_env_true("INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE") + # whether to skip waiting for the infrastructure to shut down, or exit immediately FORCE_SHUTDOWN = is_env_not_false("FORCE_SHUTDOWN") @@ -1448,7 +1450,7 @@ def is_collect_metrics_mode() -> bool: return is_env_true(ENV_INTERNAL_TEST_COLLECT_METRIC) -def collect_config_items() -> List[Tuple[str, Any]]: +def collect_config_items() -> list[tuple[str, Any]]: """Returns a list of key-value tuples of LocalStack configuration values.""" none = object() # sentinel object @@ -1598,7 +1600,7 @@ def get_edge_url(localstack_hostname=None, protocol=None): class ServiceProviderConfig(Mapping[str, str]): - _provider_config: Dict[str, str] + _provider_config: dict[str, str] default_value: str override_prefix: str = "PROVIDER_OVERRIDE_" @@ -1623,7 +1625,7 @@ def set_provider_if_not_exists(self, service: str, provider: str) -> None: def set_provider(self, service: str, provider: str): self._provider_config[service] = provider - def bulk_set_provider_if_not_exists(self, services: List[str], provider: str): + def bulk_set_provider_if_not_exists(self, services: list[str], provider: str): for service in services: self.set_provider_if_not_exists(service, provider) diff --git a/localstack-core/localstack/deprecations.py b/localstack-core/localstack/deprecations.py index 1690ca227d878..b9713f06d23e5 100644 --- a/localstack-core/localstack/deprecations.py +++ b/localstack-core/localstack/deprecations.py @@ -1,8 +1,8 @@ # A simple module to track deprecations over time / versions, and some simple functions guiding the affected users. import logging import os +from collections.abc import Callable from dataclasses import dataclass -from typing import Callable, List, Optional from localstack.utils.analytics import log @@ -329,8 +329,8 @@ def is_affected(self) -> bool: def collect_affected_deprecations( - deprecations: Optional[List[EnvVarDeprecation]] = None, -) -> List[EnvVarDeprecation]: + deprecations: list[EnvVarDeprecation] | None = None, +) -> list[EnvVarDeprecation]: """ Collects all deprecations which are used in the OS environ. :param deprecations: List of deprecations to check. Uses DEPRECATIONS list by default. @@ -341,7 +341,7 @@ def collect_affected_deprecations( return [deprecation for deprecation in deprecations if deprecation.is_affected] -def log_env_warning(deprecations: List[EnvVarDeprecation]) -> None: +def log_env_warning(deprecations: list[EnvVarDeprecation]) -> None: """ Logs warnings for the given deprecations. :param deprecations: list of affected deprecations to show a warning for @@ -368,7 +368,7 @@ def log_env_warning(deprecations: List[EnvVarDeprecation]) -> None: log.event(event="deprecated_env_usage", payload={"deprecated_env_vars": env_vars}) -def log_deprecation_warnings(deprecations: Optional[List[EnvVarDeprecation]] = None) -> None: +def log_deprecation_warnings(deprecations: list[EnvVarDeprecation] | None = None) -> None: affected_deprecations = collect_affected_deprecations(deprecations) log_env_warning(affected_deprecations) diff --git a/localstack-core/localstack/dev/run/__main__.py b/localstack-core/localstack/dev/run/__main__.py index 35cf02b953f8d..ae6ad89e04c8d 100644 --- a/localstack-core/localstack/dev/run/__main__.py +++ b/localstack-core/localstack/dev/run/__main__.py @@ -1,6 +1,6 @@ import dataclasses import os -from typing import Iterable, Tuple +from collections.abc import Iterable import click from rich.rule import Rule @@ -133,9 +133,9 @@ def run( mount_dependencies: bool = False, mount_entrypoints: bool = False, mount_docker_socket: bool = True, - env: Tuple = (), - volume: Tuple = (), - publish: Tuple = (), + env: tuple = (), + volume: tuple = (), + publish: tuple = (), entrypoint: str = None, network: str = None, local_packages: list[str] | None = None, @@ -266,7 +266,7 @@ def run( remove=True, interactive=True, tty=True, - env_vars=dict(), + env_vars={}, volumes=VolumeMappings(), ports=PortMappings(), network=network, diff --git a/localstack-core/localstack/dev/run/paths.py b/localstack-core/localstack/dev/run/paths.py index 963a03cdbcdee..00c7c647fc424 100644 --- a/localstack-core/localstack/dev/run/paths.py +++ b/localstack-core/localstack/dev/run/paths.py @@ -1,8 +1,8 @@ """Utilities to resolve important paths on the host and in the container.""" import os +from collections.abc import Callable from pathlib import Path -from typing import Callable, Optional, Union class HostPaths: @@ -20,9 +20,9 @@ class HostPaths: def __init__( self, - workspace_dir: Union[os.PathLike, str] = None, - volume_dir: Union[os.PathLike, str] = None, - venv_dir: Union[os.PathLike, str] = None, + workspace_dir: os.PathLike | str = None, + volume_dir: os.PathLike | str = None, + venv_dir: os.PathLike | str = None, ): self.workspace_dir = Path(workspace_dir or os.path.abspath(os.path.join(os.getcwd(), ".."))) self.localstack_project_dir = self.workspace_dir / "localstack" @@ -72,7 +72,7 @@ class ContainerPaths: docker_entrypoint: str = "/usr/local/bin/docker-entrypoint.sh" localstack_supervisor: str = "/usr/local/bin/localstack-supervisor" localstack_source_dir: str - localstack_pro_source_dir: Optional[str] + localstack_pro_source_dir: str | None def dependency_source(self, name: str) -> str: """Returns path of the given source dependency in the site-packages directory.""" diff --git a/localstack-core/localstack/dns/models.py b/localstack-core/localstack/dns/models.py index 6df70bf6e0d86..99e57efd7553e 100644 --- a/localstack-core/localstack/dns/models.py +++ b/localstack-core/localstack/dns/models.py @@ -1,6 +1,7 @@ import dataclasses +from collections.abc import Callable from enum import Enum, auto -from typing import Callable, Protocol +from typing import Protocol class RecordType(Enum): diff --git a/localstack-core/localstack/dns/server.py b/localstack-core/localstack/dns/server.py index f32d81292c75e..c7d5cb8154d9e 100644 --- a/localstack-core/localstack/dns/server.py +++ b/localstack-core/localstack/dns/server.py @@ -5,12 +5,13 @@ import re import textwrap import threading +from collections.abc import Iterable from datetime import datetime from functools import cache from ipaddress import IPv4Address, IPv4Interface from pathlib import Path from socket import AddressFamily -from typing import Iterable, Literal, Tuple +from typing import Literal import psutil from cachetools import TTLCache, cached @@ -90,7 +91,7 @@ # Type of the value given by DNSHandler.client_address # in the form (ip, port) e.g. ("127.0.0.1", 58291) -ClientAddress = Tuple[str, int] +ClientAddress = tuple[str, int] psutil_cache = TTLCache(maxsize=100, ttl=10) diff --git a/localstack-core/localstack/http/dispatcher.py b/localstack-core/localstack/http/dispatcher.py index 308450fbd3296..6568080f1fa3a 100644 --- a/localstack-core/localstack/http/dispatcher.py +++ b/localstack-core/localstack/http/dispatcher.py @@ -1,5 +1,4 @@ from json import JSONEncoder -from typing import Type from rolo.routing.handler import Handler, ResultValue from rolo.routing.handler import handler_dispatcher as _handler_dispatcher @@ -14,7 +13,7 @@ ] -def handler_dispatcher(json_encoder: Type[JSONEncoder] = None) -> Dispatcher[Handler]: +def handler_dispatcher(json_encoder: type[JSONEncoder] = None) -> Dispatcher[Handler]: """ Replacement for ``rolo.dispatcher.handler_dispatcher`` that uses by default LocalStack's CustomEncoder for serializing JSON documents. diff --git a/localstack-core/localstack/http/response.py b/localstack-core/localstack/http/response.py index 66863c147d370..c0d9285601669 100644 --- a/localstack-core/localstack/http/response.py +++ b/localstack-core/localstack/http/response.py @@ -1,5 +1,5 @@ from json import JSONEncoder -from typing import Any, Type +from typing import Any from rolo import Response as RoloResponse @@ -11,7 +11,7 @@ class Response(RoloResponse): An HTTP Response object, which simply extends werkzeug's Response object with a few convenience methods. """ - def set_json(self, doc: Any, cls: Type[JSONEncoder] = CustomEncoder): + def set_json(self, doc: Any, cls: type[JSONEncoder] = CustomEncoder): """ Serializes the given dictionary using localstack's ``CustomEncoder`` into a json response, and sets the mimetype automatically to ``application/json``. diff --git a/localstack-core/localstack/http/router.py b/localstack-core/localstack/http/router.py index da3bcdfe043c0..ffa46e31280db 100644 --- a/localstack-core/localstack/http/router.py +++ b/localstack-core/localstack/http/router.py @@ -1,6 +1,6 @@ +from collections.abc import Mapping from typing import ( Any, - Mapping, TypeVar, ) diff --git a/localstack-core/localstack/http/trace.py b/localstack-core/localstack/http/trace.py index 7d52b9ebf36dc..e15c6b2caf004 100644 --- a/localstack-core/localstack/http/trace.py +++ b/localstack-core/localstack/http/trace.py @@ -2,7 +2,8 @@ import inspect import logging import time -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from rolo import Response from rolo.gateway import ExceptionHandler, Handler, HandlerChain, RequestContext diff --git a/localstack-core/localstack/logging/format.py b/localstack-core/localstack/logging/format.py index 5f308e34d9ecf..1c6a2c37cac02 100644 --- a/localstack-core/localstack/logging/format.py +++ b/localstack-core/localstack/logging/format.py @@ -3,7 +3,7 @@ import logging import re from functools import lru_cache -from typing import Any, Dict +from typing import Any from localstack.utils.numbers import format_bytes from localstack.utils.strings import to_bytes @@ -173,8 +173,8 @@ class AwsTraceLoggingFormatter(TraceLoggingFormatter): def __init__(self): super().__init__() - def _copy_service_dict(self, service_dict: Dict) -> Dict: - if not isinstance(service_dict, Dict): + def _copy_service_dict(self, service_dict: dict) -> dict: + if not isinstance(service_dict, dict): return service_dict result = {} for key, value in service_dict.items(): diff --git a/localstack-core/localstack/packages/api.py b/localstack-core/localstack/packages/api.py index bcc8add9577c5..04d50b0012f87 100644 --- a/localstack-core/localstack/packages/api.py +++ b/localstack-core/localstack/packages/api.py @@ -6,7 +6,7 @@ from enum import Enum from inspect import getmodule from threading import RLock -from typing import Any, Callable, Generic, List, Optional, ParamSpec, TypeVar +from typing import Any, Callable, Generic, Optional, ParamSpec, TypeVar from plux import Plugin, PluginManager, PluginSpec # type: ignore @@ -219,7 +219,7 @@ def install(self, version: str | None = None, target: Optional[InstallTarget] = """ self.get_installer(version).install(target) - @functools.lru_cache() + @functools.lru_cache def get_installer(self, version: str | None = None) -> T: """ Returns the installer instance for a specific version of the package. @@ -237,7 +237,7 @@ def get_installer(self, version: str | None = None) -> T: raise NoSuchVersionException(package=self.name, version=version) return self._get_installer(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: """ :return: List of all versions available for this package. """ @@ -262,7 +262,7 @@ class MultiPackageInstaller(PackageInstaller): PackageInstaller implementation which composes of multiple package installers. """ - def __init__(self, name: str, version: str, package_installer: List[PackageInstaller]): + def __init__(self, name: str, version: str, package_installer: list[PackageInstaller]): """ :param name: of the (multi-)package installer :param version: of this (multi-)package installer @@ -317,7 +317,7 @@ def __init__( self, name: str, scope: str, - get_package: Callable[[], Package[PackageInstaller] | List[Package[PackageInstaller]]], + get_package: Callable[[], Package[PackageInstaller] | list[Package[PackageInstaller]]], should_load: Callable[[], bool] | None = None, ) -> None: super().__init__() diff --git a/localstack-core/localstack/packages/core.py b/localstack-core/localstack/packages/core.py index 40844a88ad9db..fcc9ed180e19b 100644 --- a/localstack-core/localstack/packages/core.py +++ b/localstack-core/localstack/packages/core.py @@ -4,7 +4,7 @@ from abc import ABC from functools import lru_cache from sys import version_info -from typing import Any, Optional, Tuple +from typing import Any, Optional import requests @@ -190,7 +190,7 @@ def __init__(self, name: str, tag: str, github_slug: str): f"https://api.github.com/repos/{github_slug}/releases/tags/{self.version}" ) - @lru_cache() + @lru_cache def _get_download_url(self) -> str: asset_name = self._get_github_asset_name() # try to use a token when calling the GH API for increased API rate limits @@ -404,7 +404,7 @@ def _install(self, target: InstallTarget) -> None: super()._install(target) -def parse_maven_package_url(package_url: str) -> Tuple[str, str, str]: +def parse_maven_package_url(package_url: str) -> tuple[str, str, str]: """Example: parse_maven_package_url("pkg:maven/software.amazon.event.ruler/event-ruler@1.7.3") -> software.amazon.event.ruler, event-ruler, 1.7.3 """ diff --git a/localstack-core/localstack/packages/debugpy.py b/localstack-core/localstack/packages/debugpy.py index dd24abfbbbf21..a31a08f5725fc 100644 --- a/localstack-core/localstack/packages/debugpy.py +++ b/localstack-core/localstack/packages/debugpy.py @@ -1,5 +1,3 @@ -from typing import List - from localstack.packages import InstallTarget, Package, PackageInstaller from localstack.utils.run import run @@ -8,7 +6,7 @@ class DebugPyPackage(Package["DebugPyPackageInstaller"]): def __init__(self) -> None: super().__init__("DebugPy", "latest") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["latest"] def _get_installer(self, version: str) -> "DebugPyPackageInstaller": diff --git a/localstack-core/localstack/packages/ffmpeg.py b/localstack-core/localstack/packages/ffmpeg.py index 230d114347b68..a503016754803 100644 --- a/localstack-core/localstack/packages/ffmpeg.py +++ b/localstack-core/localstack/packages/ffmpeg.py @@ -1,5 +1,4 @@ import os -from typing import List from localstack.packages import Package from localstack.packages.core import ArchiveDownloadAndExtractInstaller @@ -21,7 +20,7 @@ def __init__(self) -> None: def _get_installer(self, version: str) -> "FfmpegPackageInstaller": return FfmpegPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["7.1"] diff --git a/localstack-core/localstack/packages/java.py b/localstack-core/localstack/packages/java.py index da0ef0110c348..83ed684e8d49a 100644 --- a/localstack-core/localstack/packages/java.py +++ b/localstack-core/localstack/packages/java.py @@ -1,6 +1,5 @@ import logging import os -from typing import List import requests @@ -23,6 +22,7 @@ "11": "11.0.25+9", "17": "17.0.13+11", "21": "21.0.5+11", + "24": "24.0.1+9", } @@ -115,23 +115,49 @@ def _post_process(self, target: InstallTarget) -> None: # Build a custom JRE with only the necessary bits to minimise disk footprint LOG.debug("Optimising JRE installation") - cmd = ( - "bin/jlink --add-modules " + + base_modules = [ # Required modules - "java.base,java.desktop,java.instrument,java.management," - "java.naming,java.scripting,java.sql,java.xml,jdk.compiler," + "java.base", + "java.desktop", + "java.instrument", + "java.management", + "java.naming", + "java.scripting", + "java.sql", + "java.xml", + "jdk.compiler", # jdk.unsupported contains sun.misc.Unsafe which is required by some dependencies - "jdk.unsupported," + "jdk.unsupported", # Additional cipher suites - "jdk.crypto.cryptoki," + "jdk.crypto.cryptoki", # Archive support - "jdk.zipfs," + "jdk.zipfs", # Required by MQ broker - "jdk.httpserver,jdk.management,jdk.management.agent," + "jdk.httpserver", + "jdk.management", + "jdk.management.agent", # Required by Spark and Hadoop - "java.security.jgss,jdk.security.auth," + "java.security.jgss", + "jdk.security.auth", + # Include required locales + "jdk.localedata", + ] + + # Add version-specific modules, not all versions require/support the same set + version_specific_modules = { + "24": ["jdk.incubator.vector"], # Required for Trino latest version + } + + modules = base_modules + version_specific_modules.get(self.version, []) + modules_str = ",".join(modules) + + cmd = ( + "bin/jlink " + # Add modules + f"--add-modules {modules_str} " # Include required locales - "jdk.localedata --include-locales en " + "--include-locales en " # Supplementary args "--compress 2 --strip-debug --no-header-files --no-man-pages " # Output directory @@ -198,7 +224,7 @@ class JavaPackage(Package[JavaPackageInstaller]): def __init__(self, default_version: str = DEFAULT_JAVA_VERSION): super().__init__(name="Java", default_version=default_version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return list(JAVA_VERSIONS.keys()) def _get_installer(self, version: str) -> JavaPackageInstaller: diff --git a/localstack-core/localstack/packages/terraform.py b/localstack-core/localstack/packages/terraform.py index 2a5da95b8472a..5d5ad321e0a61 100644 --- a/localstack-core/localstack/packages/terraform.py +++ b/localstack-core/localstack/packages/terraform.py @@ -1,6 +1,5 @@ import os import platform -from typing import List from localstack.packages import InstallTarget, Package from localstack.packages.core import ArchiveDownloadAndExtractInstaller @@ -20,7 +19,7 @@ class TerraformPackage(Package["TerraformPackageInstaller"]): def __init__(self) -> None: super().__init__("Terraform", TERRAFORM_VERSION) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [TERRAFORM_VERSION] def _get_installer(self, version: str) -> "TerraformPackageInstaller": diff --git a/localstack-core/localstack/runtime/init.py b/localstack-core/localstack/runtime/init.py index e9b2f97dccf9e..d1398c3840430 100644 --- a/localstack-core/localstack/runtime/init.py +++ b/localstack-core/localstack/runtime/init.py @@ -7,7 +7,6 @@ import time from enum import Enum from functools import cached_property -from typing import Dict, List, Optional from plux import Plugin, PluginManager @@ -109,7 +108,7 @@ def run(self, path: str) -> None: class InitScriptManager: - _stage_directories: Dict[Stage, str] = { + _stage_directories: dict[Stage, str] = { Stage.BOOT: "boot.d", Stage.START: "start.d", Stage.READY: "ready.d", @@ -117,7 +116,7 @@ class InitScriptManager: } script_root: str - stage_completed: Dict[Stage, bool] + stage_completed: dict[Stage, bool] def __init__(self, script_root: str): self.script_root = script_root @@ -125,10 +124,10 @@ def __init__(self, script_root: str): self.runner_manager: PluginManager[ScriptRunner] = PluginManager(ScriptRunner.namespace) @cached_property - def scripts(self) -> Dict[Stage, List[Script]]: + def scripts(self) -> dict[Stage, list[Script]]: return self._find_scripts() - def get_script_runner(self, script_file: str) -> Optional[ScriptRunner]: + def get_script_runner(self, script_file: str) -> ScriptRunner | None: runners = self.runner_manager.load_all() for runner in runners: if runner.should_run(script_file): @@ -138,7 +137,7 @@ def get_script_runner(self, script_file: str) -> Optional[ScriptRunner]: def has_script_runner(self, script_file: str) -> bool: return self.get_script_runner(script_file) is not None - def run_stage(self, stage: Stage) -> List[Script]: + def run_stage(self, stage: Stage) -> list[Script]: """ Runs all scripts in the given stage. @@ -188,7 +187,7 @@ def run_stage(self, stage: Stage) -> List[Script]: return scripts - def _find_scripts(self) -> Dict[Stage, List[Script]]: + def _find_scripts(self) -> dict[Stage, list[Script]]: scripts = {} if self.script_root is None: diff --git a/localstack-core/localstack/runtime/shutdown.py b/localstack-core/localstack/runtime/shutdown.py index a64dab86ef930..6d9cfb5e094b6 100644 --- a/localstack-core/localstack/runtime/shutdown.py +++ b/localstack-core/localstack/runtime/shutdown.py @@ -1,5 +1,6 @@ import logging -from typing import Any, Callable +from collections.abc import Callable +from typing import Any from localstack.runtime import hooks from localstack.utils.functions import call_safe diff --git a/localstack-core/localstack/services/apigateway/exporter.py b/localstack-core/localstack/services/apigateway/exporter.py index 0706e794c1651..c0fdc0b456a9e 100644 --- a/localstack-core/localstack/services/apigateway/exporter.py +++ b/localstack-core/localstack/services/apigateway/exporter.py @@ -1,6 +1,5 @@ import abc import json -from typing import Type from apispec import APISpec @@ -320,7 +319,7 @@ def export( class OpenApiExporter: - exporters: dict[str, Type[_BaseOpenApiExporter]] + exporters: dict[str, type[_BaseOpenApiExporter]] def __init__(self): self.exporters = {"swagger": _OpenApiSwaggerExporter, "oas30": _OpenApiOAS30Exporter} diff --git a/localstack-core/localstack/services/apigateway/helpers.py b/localstack-core/localstack/services/apigateway/helpers.py index 8e69a9218e6e2..385aae2b02c81 100644 --- a/localstack-core/localstack/services/apigateway/helpers.py +++ b/localstack-core/localstack/services/apigateway/helpers.py @@ -3,7 +3,7 @@ import hashlib import json import logging -from typing import List, Optional, TypedDict, Union +from typing import TypedDict from urllib import parse as urlparse from jsonpatch import apply_patch @@ -93,7 +93,7 @@ class OpenAPIExt: class AuthorizerConfig(TypedDict): authorizer: Authorizer - authorization_scopes: Optional[list[str]] + authorization_scopes: list[str] | None # TODO: make the CRUD operations in this file generic for the different model types (authorizes, validators, ...) @@ -198,11 +198,11 @@ def _resolve_refpath(self, refpath: str) -> dict: return cur - def _namespaced_resolution(self, namespace: str, data: Union[dict, list]) -> Union[dict, list]: + def _namespaced_resolution(self, namespace: str, data: dict | list) -> dict | list: with self._pathctx(namespace): return self._resolve_references(data) - def _resolve_references(self, data) -> Union[dict, list]: + def _resolve_references(self, data) -> dict | list: if self._is_ref(data): return self._resolve_refpath(data["$ref"]) @@ -578,7 +578,7 @@ def create_authorizers(security_schemes: dict) -> None: authorizers[security_scheme_name] = authorizer - def get_authorizer(path_payload: dict) -> Optional[AuthorizerConfig]: + def get_authorizer(path_payload: dict) -> AuthorizerConfig | None: if not (security_schemes := path_payload.get("security")): return None @@ -605,7 +605,7 @@ def get_or_create_path(abs_path: str, base_path: str): rel_path = abs_path.removeprefix(base_path) return add_path_methods(rel_path, parts, parent_id=parent_id) - def add_path_methods(rel_path: str, parts: List[str], parent_id=""): + def add_path_methods(rel_path: str, parts: list[str], parent_id=""): rel_path = rel_path or "/" child_id = ApigwResourceIdentifier(account_id, region_name, parent_id, rel_path).generate() diff --git a/localstack-core/localstack/services/apigateway/legacy/context.py b/localstack-core/localstack/services/apigateway/legacy/context.py index 37b9725f3feb8..9c6e75b424a8d 100644 --- a/localstack-core/localstack/services/apigateway/legacy/context.py +++ b/localstack-core/localstack/services/apigateway/legacy/context.py @@ -1,7 +1,7 @@ import base64 import json from enum import Enum -from typing import Any, Dict, List, Optional, Union +from typing import Any from responses import Response @@ -10,7 +10,7 @@ from localstack.utils.strings import short_uid, to_str # type definition for data parameters (i.e., invocation payloads) -InvocationPayload = Union[Dict, str, bytes] +InvocationPayload = dict | str | bytes class ApiGatewayVersion(Enum): @@ -25,15 +25,15 @@ class ApiInvocationContext: method: str path: str data: InvocationPayload - headers: Dict[str, str] + headers: dict[str, str] # raw URI (including query string) retired from werkzeug "RAW_URI" environment variable raw_uri: str # invocation context - context: Dict[str, Any] + context: dict[str, Any] # authentication info for this invocation - auth_context: Dict[str, Any] + auth_context: dict[str, Any] # target API/resource details extracted from the invocation apigw_version: ApiGatewayVersion @@ -43,24 +43,24 @@ class ApiInvocationContext: region_name: str # resource path, including any path parameter placeholders (e.g., "/my/path/{id}") resource_path: str - integration: Dict - resource: Dict + integration: dict + resource: dict # Invocation path with query string, e.g., "/my/path?test". Defaults to "path", can be used # to overwrite the actual API path, in case the path format "../_user_request_/.." is used. _path_with_query_string: str # response templates to be applied to the invocation result - response_templates: Dict + response_templates: dict - route: Dict + route: dict connection_id: str - path_params: Dict + path_params: dict # response object response: Response # dict of stage variables (mapping names to values) - stage_variables: Dict[str, str] + stage_variables: dict[str, str] # websockets route selection ws_route: str @@ -69,12 +69,12 @@ def __init__( self, method: str, path: str, - data: Union[str, bytes], - headers: Dict[str, str], + data: str | bytes, + headers: dict[str, str], api_id: str = None, stage: str = None, - context: Dict[str, Any] = None, - auth_context: Dict[str, Any] = None, + context: dict[str, Any] = None, + auth_context: dict[str, Any] = None, ): self.method = method self._path = path @@ -109,7 +109,7 @@ def path(self, new_path: str): self._path = new_path @property - def resource_id(self) -> Optional[str]: + def resource_id(self) -> str | None: return (self.resource or {}).get("id") @property @@ -130,18 +130,18 @@ def path_with_query_string(self, new_path: str): new_path = "/" + new_path.lstrip("/") self._path_with_query_string = new_path - def query_params(self) -> Dict[str, str]: + def query_params(self) -> dict[str, str]: """Extract the query parameters from the target URL or path in this request context.""" query_string = self.path_with_query_string.partition("?")[2] return parse_query_string(query_string) @property - def integration_uri(self) -> Optional[str]: + def integration_uri(self) -> str | None: integration = self.integration or {} return integration.get("uri") or integration.get("integrationUri") @property - def auth_identity(self) -> Optional[Dict]: + def auth_identity(self) -> dict | None: if isinstance(self.auth_context, dict): if self.auth_context.get("identity") is None: self.auth_context["identity"] = {} @@ -153,7 +153,7 @@ def authorizer_type(self) -> str: return self.auth_context.get("authorizer_type") if self.auth_context else None @property - def authorizer_result(self) -> Dict[str, Any]: + def authorizer_result(self) -> dict[str, Any]: if isinstance(self.auth_context, dict): return self.auth_context.get("authorizer") if self.auth_context else {} @@ -165,7 +165,7 @@ def is_v1(self) -> bool: """Whether this is an API Gateway v1 request""" return self.apigw_version == ApiGatewayVersion.V1 - def cookies(self) -> Optional[List[str]]: + def cookies(self) -> list[str] | None: if cookies := self.headers.get("cookie") or "": return list(cookies.split(";")) return None diff --git a/localstack-core/localstack/services/apigateway/legacy/helpers.py b/localstack-core/localstack/services/apigateway/legacy/helpers.py index 62a91a32e78b0..00a32802a897d 100644 --- a/localstack-core/localstack/services/apigateway/legacy/helpers.py +++ b/localstack-core/localstack/services/apigateway/legacy/helpers.py @@ -3,8 +3,8 @@ import re import time from collections import defaultdict -from datetime import datetime, timezone -from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union +from datetime import UTC, datetime +from typing import Any, TypedDict from urllib import parse as urlparse from botocore.utils import InvalidArnException @@ -90,7 +90,7 @@ def resolve(self, context: ApiInvocationContext) -> IntegrationParameters: :return: IntegrationParameters """ - method_request_params: Dict[str, Any] = self.method_request_dict(context) + method_request_params: dict[str, Any] = self.method_request_dict(context) # requestParameters: { # "integration.request.path.pathParam": "method.request.header.Content-Type" @@ -128,13 +128,13 @@ def resolve(self, context: ApiInvocationContext) -> IntegrationParameters: return result - def method_request_dict(self, context: ApiInvocationContext) -> Dict[str, Any]: + def method_request_dict(self, context: ApiInvocationContext) -> dict[str, Any]: """ Build a dict with all method request parameters and their values. :return: dict with all method request parameters and their values, and all keys in lowercase """ - params: Dict[str, str] = {} + params: dict[str, str] = {} # TODO: add support for multi-values headers and multi-values querystring @@ -183,7 +183,7 @@ def method_request_dict(self, context: ApiInvocationContext) -> Dict[str, Any]: class ResponseParametersResolver: - def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: + def resolve(self, context: ApiInvocationContext) -> dict[str, str]: """ Resolve integration response parameters into method response parameters. Integration response parameters can map header, body, @@ -191,7 +191,7 @@ def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: :return: dict with all method response parameters and their values """ - integration_request_params: Dict[str, Any] = self.integration_request_dict(context) + integration_request_params: dict[str, Any] = self.integration_request_dict(context) # "responseParameters" : { # "method.response.header.Location" : "integration.response.body.redirect.url", @@ -213,7 +213,7 @@ def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: method_parameters[k] = v.replace("'", "") # build the integration parameters - result: Dict[str, str] = {} + result: dict[str, str] = {} for k, v in method_parameters.items(): # headers if k.startswith("method.response.header."): @@ -222,8 +222,8 @@ def resolve(self, context: ApiInvocationContext) -> Dict[str, str]: return result - def integration_request_dict(self, context: ApiInvocationContext) -> Dict[str, Any]: - params: Dict[str, str] = {} + def integration_request_dict(self, context: ApiInvocationContext) -> dict[str, Any]: + params: dict[str, str] = {} for k, v in context.headers.items(): params[f"integration.request.header.{k}"] = v @@ -293,7 +293,7 @@ def is_test_invoke_method(method, path): return method == "POST" and bool(re.match(PATH_REGEX_TEST_INVOKE_API, path)) -def get_stage_variables(context: ApiInvocationContext) -> Optional[Dict[str, str]]: +def get_stage_variables(context: ApiInvocationContext) -> dict[str, str] | None: if is_test_invoke_method(context.method, context.path): return None @@ -316,7 +316,7 @@ def tokenize_path(path): return path.lstrip("/").split("/") -def extract_path_params(path: str, extracted_path: str) -> Dict[str, str]: +def extract_path_params(path: str, extracted_path: str) -> dict[str, str]: tokenized_extracted_path = tokenize_path(extracted_path) # Looks for '{' in the tokenized extracted path path_params_list = [(i, v) for i, v in enumerate(tokenized_extracted_path) if "{" in v] @@ -335,7 +335,7 @@ def extract_path_params(path: str, extracted_path: str) -> Dict[str, str]: return path_params -def extract_query_string_params(path: str) -> Tuple[str, Dict[str, str]]: +def extract_query_string_params(path: str) -> tuple[str, dict[str, str]]: parsed_path = urlparse.urlparse(path) if not path.startswith("//"): path = parsed_path.path @@ -411,8 +411,8 @@ def get_rest_api_paths(account_id: str, region_name: str, rest_api_id: str): # -method-request.html # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html def get_resource_for_path( - path: str, method: str, path_map: Dict[str, Dict] -) -> tuple[Optional[str], Optional[dict]]: + path: str, method: str, path_map: dict[str, dict] +) -> tuple[str | None, dict | None]: matches = [] # creates a regex from the input path if there are parameters, e.g /foo/{bar}/baz -> /foo/[ # ^\]+/baz, otherwise is a direct match. @@ -528,7 +528,7 @@ def connect_api_gateway_to_sqs(gateway_name, stage_name, queue_arn, path, accoun def get_target_resource_details( invocation_context: ApiInvocationContext, -) -> Tuple[Optional[str], Optional[dict]]: +) -> tuple[str | None, dict | None]: """Look up and return the API GW resource (path pattern + resource dict) for the given invocation context.""" path_map = get_rest_api_paths( account_id=invocation_context.account_id, @@ -557,7 +557,7 @@ def get_target_resource_details( return None, None -def get_target_resource_method(invocation_context: ApiInvocationContext) -> Optional[Dict]: +def get_target_resource_method(invocation_context: ApiInvocationContext) -> dict | None: """Look up and return the API GW resource method for the given invocation context.""" _, resource = get_target_resource_details(invocation_context) if not resource: @@ -614,7 +614,7 @@ def get_event_request_context(invocation_context: ApiInvocationContext): }, "httpMethod": method, "protocol": "HTTP/1.1", - "requestTime": datetime.now(timezone.utc).strftime(REQUEST_TIME_DATE_FORMAT), + "requestTime": datetime.now(UTC).strftime(REQUEST_TIME_DATE_FORMAT), "requestTimeEpoch": int(time.time() * 1000), "authorizer": {}, } @@ -681,7 +681,7 @@ def set_api_id_stage_invocation_path( return invocation_context -def get_api_account_id_and_region(api_id: str) -> Tuple[Optional[str], Optional[str]]: +def get_api_account_id_and_region(api_id: str) -> tuple[str | None, str | None]: """Return the region name for the given REST API ID""" for account_id, account in apigateway_backends.items(): for region_name, region in account.items(): @@ -698,7 +698,7 @@ def extract_api_id_from_hostname_in_url(hostname: str) -> str: return match.group(1) -def multi_value_dict_for_list(elements: Union[List, Dict]) -> Dict: +def multi_value_dict_for_list(elements: list | dict) -> dict: temp_mv_dict = defaultdict(list) for key in elements: if isinstance(key, (list, tuple)): diff --git a/localstack-core/localstack/services/apigateway/legacy/integration.py b/localstack-core/localstack/services/apigateway/legacy/integration.py index 12852fff266af..220ec8c5ed223 100644 --- a/localstack-core/localstack/services/apigateway/legacy/integration.py +++ b/localstack-core/localstack/services/apigateway/legacy/integration.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from functools import lru_cache from http import HTTPMethod, HTTPStatus -from typing import Any, Dict +from typing import Any from urllib.parse import urljoin import requests @@ -91,7 +91,7 @@ def _create_response(cls, status_code, headers, data=""): @classmethod def apply_request_parameters( - cls, integration_params: IntegrationParameters, headers: Dict[str, Any] + cls, integration_params: IntegrationParameters, headers: dict[str, Any] ): for k, v in integration_params.get("headers").items(): headers.update({k: v}) @@ -569,7 +569,7 @@ def invoke(self, invocation_context: ApiInvocationContext): return invocation_context.response @classmethod - def _validate_required_params(cls, request_parameters: Dict[str, Any]) -> None: + def _validate_required_params(cls, request_parameters: dict[str, Any]) -> None: if not request_parameters: raise BadRequestException("Missing required parameters") # https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html#Kinesis-PutRecord @@ -588,7 +588,7 @@ def _validate_required_params(cls, request_parameters: Dict[str, Any]) -> None: def _create_request_parameters( self, invocation_context: ApiInvocationContext - ) -> Dict[str, Any]: + ) -> dict[str, Any]: request_parameters = invocation_context.integration.get("requestParameters", {}) self._validate_required_params(request_parameters) @@ -756,7 +756,7 @@ def invoke(self, invocation_context: ApiInvocationContext): class HTTPIntegration(BackendIntegration): @staticmethod - def _set_http_apigw_headers(headers: Dict[str, Any], invocation_context: ApiInvocationContext): + def _set_http_apigw_headers(headers: dict[str, Any], invocation_context: ApiInvocationContext): del headers["host"] headers["x-amzn-apigateway-api-id"] = invocation_context.api_id return headers @@ -902,7 +902,7 @@ def invoke(self, invocation_context: ApiInvocationContext) -> Response: class StepFunctionIntegration(BackendIntegration): @classmethod - def _validate_required_params(cls, request_parameters: Dict[str, Any]) -> None: + def _validate_required_params(cls, request_parameters: dict[str, Any]) -> None: if not request_parameters: raise BadRequestException("Missing required parameters") # stateMachineArn and input are required @@ -1065,7 +1065,7 @@ def invoke(self, invocation_context: ApiInvocationContext) -> Response: # TODO: remove once we migrate all usages to `apply_request_parameters` on BackendIntegration def apply_request_parameters( - uri: str, integration: Dict[str, Any], path_params: Dict[str, str], query_params: Dict[str, str] + uri: str, integration: dict[str, Any], path_params: dict[str, str], query_params: dict[str, str] ): request_parameters = integration.get("requestParameters") uri = uri or integration.get("uri") or integration.get("integrationUri") or "" diff --git a/localstack-core/localstack/services/apigateway/legacy/router_asf.py b/localstack-core/localstack/services/apigateway/legacy/router_asf.py index 0664c98c56f20..c786ba4ed2bd8 100644 --- a/localstack-core/localstack/services/apigateway/legacy/router_asf.py +++ b/localstack-core/localstack/services/apigateway/legacy/router_asf.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict +from typing import Any from requests.models import Response as RequestsResponse from werkzeug.datastructures import Headers @@ -22,7 +22,7 @@ # invocation context property decorators and use the url_params directly, # something asked for a long time. def to_invocation_context( - request: Request, url_params: Dict[str, Any] = None + request: Request, url_params: dict[str, Any] = None ) -> ApiInvocationContext: """ Converts an HTTP Request object into an ApiInvocationContext. diff --git a/localstack-core/localstack/services/apigateway/legacy/templates.py b/localstack-core/localstack/services/apigateway/legacy/templates.py index 0ae853981ac02..82df2469f0773 100644 --- a/localstack-core/localstack/services/apigateway/legacy/templates.py +++ b/localstack-core/localstack/services/apigateway/legacy/templates.py @@ -3,7 +3,7 @@ import json import logging from enum import Enum -from typing import Any, Dict, Union +from typing import Any from urllib.parse import quote_plus, unquote_plus import xmltodict @@ -184,7 +184,7 @@ def __repr__(self): class ApiGatewayVtlTemplate(VtlTemplate): """Util class for rendering VTL templates with API Gateway specific extensions""" - def prepare_namespace(self, variables, source: str = APIGW_SOURCE) -> Dict[str, Any]: + def prepare_namespace(self, variables, source: str = APIGW_SOURCE) -> dict[str, Any]: namespace = super().prepare_namespace(variables, source) if stage_var := variables.get("stage_variables") or {}: namespace["stageVariables"] = stage_var @@ -203,7 +203,7 @@ class Templates: def __init__(self): self.vtl = ApiGatewayVtlTemplate() - def render(self, api_context: ApiInvocationContext) -> Union[bytes, str]: + def render(self, api_context: ApiInvocationContext) -> bytes | str: pass def render_vtl(self, template, variables): @@ -250,7 +250,7 @@ class RequestTemplates(Templates): def render( self, api_context: ApiInvocationContext, template_key: str = APPLICATION_JSON - ) -> Union[bytes, str]: + ) -> bytes | str: LOG.debug( "Method request body before transformations: %s", to_str(api_context.data_as_string()) ) @@ -278,7 +278,7 @@ class ResponseTemplates(Templates): template is used. """ - def render(self, api_context: ApiInvocationContext, **kwargs) -> Union[bytes, str]: + def render(self, api_context: ApiInvocationContext, **kwargs) -> bytes | str: # XXX: keep backwards compatibility until we migrate all integrations to this new classes # api_context contains a response object that we want slowly remove from it data = kwargs.get("response", "") diff --git a/localstack-core/localstack/services/apigateway/models.py b/localstack-core/localstack/services/apigateway/models.py index 44fca6b65ae29..e287210f1a4f3 100644 --- a/localstack-core/localstack/services/apigateway/models.py +++ b/localstack-core/localstack/services/apigateway/models.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from requests.structures import CaseInsensitiveDict @@ -28,21 +28,21 @@ class RestApiContainer: # contains the RestApi dictionary. We're not making use of it yet, still using moto data. rest_api: RestApi # maps AuthorizerId -> Authorizer - authorizers: Dict[str, Authorizer] + authorizers: dict[str, Authorizer] # maps RequestValidatorId -> RequestValidator - validators: Dict[str, RequestValidator] + validators: dict[str, RequestValidator] # map DocumentationPartId -> DocumentationPart - documentation_parts: Dict[str, DocumentationPart] + documentation_parts: dict[str, DocumentationPart] # map doc version name -> DocumentationVersion - documentation_versions: Dict[str, DocumentationVersion] + documentation_versions: dict[str, DocumentationVersion] # not used yet, still in moto - gateway_responses: Dict[GatewayResponseType, GatewayResponse] + gateway_responses: dict[GatewayResponseType, GatewayResponse] # maps Model name -> Model - models: Dict[str, Model] + models: dict[str, Model] # maps Model name -> resolved dict Model, so we don't need to load the JSON everytime - resolved_models: Dict[str, dict] + resolved_models: dict[str, dict] # maps ResourceId of a Resource to its children ResourceIds - resource_children: Dict[str, List[str]] + resource_children: dict[str, list[str]] def __init__(self, rest_api: RestApi): self.rest_api = rest_api @@ -102,25 +102,25 @@ def __init__( class ApiGatewayStore(BaseStore): # maps (API id) -> RestApiContainer # TODO: remove CaseInsensitiveDict, and lower the value of the ID when getting it from the tags - rest_apis: Dict[str, RestApiContainer] = LocalAttribute(default=CaseInsensitiveDict) + rest_apis: dict[str, RestApiContainer] = LocalAttribute(default=CaseInsensitiveDict) # account details - _account: Dict[str, Any] = LocalAttribute(default=dict) + _account: dict[str, Any] = LocalAttribute(default=dict) # maps (domain_name) -> [path_mappings] - base_path_mappings: Dict[str, List[Dict]] = LocalAttribute(default=dict) + base_path_mappings: dict[str, list[dict]] = LocalAttribute(default=dict) # maps ID to VPC link details - vpc_links: Dict[str, Dict] = LocalAttribute(default=dict) + vpc_links: dict[str, dict] = LocalAttribute(default=dict) # maps cert ID to client certificate details - client_certificates: Dict[str, Dict] = LocalAttribute(default=dict) + client_certificates: dict[str, dict] = LocalAttribute(default=dict) # maps domain name to domain name model - domain_names: Dict[str, DomainName] = LocalAttribute(default=dict) + domain_names: dict[str, DomainName] = LocalAttribute(default=dict) # maps resource ARN to tags - TAGS: Dict[str, Dict[str, str]] = CrossRegionAttribute(default=dict) + TAGS: dict[str, dict[str, str]] = CrossRegionAttribute(default=dict) # internal deployments, represents a frozen REST API for a deployment, used in our router # TODO: make sure API ID are unique across all accounts diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py index 843938e0611ed..ec3a932b0a6b1 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/api.py @@ -1,4 +1,4 @@ -from typing import Callable, Type +from collections.abc import Callable from rolo import Response from rolo.gateway.chain import HandlerChain as RoloHandlerChain @@ -14,4 +14,4 @@ None, ] -RestApiGatewayHandlerChain: Type[RoloHandlerChain[RestApiInvocationContext]] = RoloHandlerChain +RestApiGatewayHandlerChain: type[RoloHandlerChain[RestApiInvocationContext]] = RoloHandlerChain diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py index 9f6be795d9af8..cb6bec52b23d9 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/context.py @@ -1,5 +1,5 @@ from http import HTTPMethod -from typing import Optional, TypedDict +from typing import TypedDict from rolo import Request from rolo.gateway import RequestContext @@ -14,12 +14,12 @@ class InvocationRequest(TypedDict, total=False): http_method: HTTPMethod """HTTP Method of the incoming request""" - raw_path: Optional[str] + raw_path: str | None # TODO: verify if raw_path is needed """Raw path of the incoming request with no modification, needed to keep double forward slashes""" - path: Optional[str] + path: str | None """Path of the request with no URL decoding""" - path_parameters: Optional[dict[str, str]] + path_parameters: dict[str, str] | None """Path parameters of the request""" query_string_parameters: dict[str, str] """Query string parameters of the request""" @@ -72,48 +72,48 @@ class RestApiInvocationContext(RequestContext): This context is going to be used to pass relevant information across an API Gateway invocation. """ - deployment: Optional[RestApiDeployment] + deployment: RestApiDeployment | None """Contains the invoked REST API Resources""" - integration: Optional[Integration] + integration: Integration | None """The Method Integration for the invoked request""" - api_id: Optional[str] + api_id: str | None """The REST API identifier of the invoked API""" - stage: Optional[str] + stage: str | None """The REST API stage name linked to this invocation""" - base_path: Optional[str] + base_path: str | None """The REST API base path mapped to the stage of this invocation""" - deployment_id: Optional[str] + deployment_id: str | None """The REST API deployment linked to this invocation""" - region: Optional[str] + region: str | None """The region the REST API is living in.""" - account_id: Optional[str] + account_id: str | None """The account the REST API is living in.""" - trace_id: Optional[str] + trace_id: str | None """The X-Ray trace ID for the request.""" - resource: Optional[Resource] + resource: Resource | None """The resource the invocation matched""" - resource_method: Optional[Method] + resource_method: Method | None """The method of the resource the invocation matched""" - stage_variables: Optional[dict[str, str]] + stage_variables: dict[str, str] | None """The Stage variables, also used in parameters mapping and mapping templates""" - stage_configuration: Optional[Stage] + stage_configuration: Stage | None """The Stage configuration, containing canary deployment settings""" - is_canary: Optional[bool] + is_canary: bool | None """If the current call was directed to a canary deployment""" - context_variables: Optional[ContextVariables] + context_variables: ContextVariables | None """The $context used in data models, authorizers, mapping templates, and CloudWatch access logging""" - context_variable_overrides: Optional[ContextVariableOverrides] + context_variable_overrides: ContextVariableOverrides | None """requestOverrides and responseOverrides are passed from request templates to response templates but are not in the integration context""" - logging_context_variables: Optional[LoggingContextVariables] + logging_context_variables: LoggingContextVariables | None """Additional $context variables available only for access logging, not yet implemented""" - invocation_request: Optional[InvocationRequest] + invocation_request: InvocationRequest | None """Contains the data relative to the invocation request""" - integration_request: Optional[IntegrationRequest] + integration_request: IntegrationRequest | None """Contains the data needed to construct an HTTP request to an Integration""" - endpoint_response: Optional[EndpointResponse] + endpoint_response: EndpointResponse | None """Contains the data returned by an Integration""" - invocation_response: Optional[InvocationResponse] + invocation_response: InvocationResponse | None """Contains the data serialized and to be returned by an invocation""" def __init__(self, request: Request): diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py index ba8ada9769f17..1030f21b6c18a 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/api_key_validation.py @@ -1,5 +1,4 @@ import logging -from typing import Optional from localstack.aws.api.apigateway import ApiKey, ApiKeySourceType, RestApi from localstack.http import Response @@ -56,9 +55,7 @@ def __call__( LOG.debug("Updating $context.identity.apiKeyId='%s'", validated_key["id"]) identity["apiKeyId"] = validated_key["id"] - def validate_api_key( - self, api_key_value, context: RestApiInvocationContext - ) -> Optional[ApiKey]: + def validate_api_key(self, api_key_value, context: RestApiInvocationContext) -> ApiKey | None: api_id = context.api_id stage = context.stage account_id = context.account_id @@ -95,7 +92,7 @@ def validate_api_key( def get_request_api_key( self, rest_api: RestApi, request: InvocationRequest, identity: ContextVarsIdentity - ) -> Optional[str]: + ) -> str | None: """https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-key-source.html The source of the API key for metering requests according to a usage plan. Valid values are: diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py index 3da898bf8845e..16a2b16a0c179 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/parse.py @@ -2,7 +2,6 @@ import logging import re from collections import defaultdict -from typing import Optional from urllib.parse import urlparse from rolo.request import restore_payload @@ -178,7 +177,7 @@ def create_context_variables(context: RestApiInvocationContext) -> ContextVariab return context_variables @staticmethod - def get_stage_variables(context: RestApiInvocationContext) -> Optional[dict[str, str]]: + def get_stage_variables(context: RestApiInvocationContext) -> dict[str, str] | None: stage_variables = context.stage_configuration.get("variables") if context.is_canary: overrides = ( diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py index 4dfe6f95dbcbe..f508015f818fd 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py @@ -1,7 +1,7 @@ import logging +from collections.abc import Iterable from functools import cache from http import HTTPMethod -from typing import Iterable from werkzeug.exceptions import MethodNotAllowed, NotFound from werkzeug.routing import Map, MapAdapter, Rule diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py index 1b1fcbfa3f35a..84c4108665c9a 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/header_utils.py @@ -1,6 +1,6 @@ import logging from collections import defaultdict -from typing import Iterable +from collections.abc import Iterable from werkzeug.datastructures.headers import Headers diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py index 33999b69ea1a9..403208a84d0fa 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/helpers.py @@ -4,7 +4,7 @@ import re import time from secrets import token_hex -from typing import Type, TypedDict +from typing import TypedDict from moto.apigateway.models import RestAPI as MotoRestAPI @@ -115,7 +115,7 @@ def get_lambda_function_arn_from_invocation_uri(uri: str) -> str: return uri.split("functions/")[1].removesuffix("/invocations") -def validate_sub_dict_of_typed_dict(typed_dict: Type[TypedDict], obj: dict) -> bool: +def validate_sub_dict_of_typed_dict(typed_dict: type[TypedDict], obj: dict) -> bool: """ Validate that the object is a subset off the keys of a given `TypedDict`. :param typed_dict: the `TypedDict` blueprint diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py index 5e65458ed4ac3..6ad97a1a067bf 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/aws.py @@ -3,7 +3,7 @@ import logging from functools import lru_cache from http import HTTPMethod -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from urllib.parse import urlparse import requests @@ -51,11 +51,11 @@ class LambdaProxyResponse(TypedDict, total=False): - body: Optional[str] - statusCode: Optional[int | str] - headers: Optional[dict[str, str]] - isBase64Encoded: Optional[bool] - multiValueHeaders: Optional[dict[str, list[str]]] + body: str | None + statusCode: int | str | None + headers: dict[str, str] | None + isBase64Encoded: bool | None + multiValueHeaders: dict[str, list[str]] | None class LambdaInputEvent(TypedDict, total=False): diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py index fa0511072c9d1..da60b9d1fa33a 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/integrations/http.py @@ -1,6 +1,6 @@ import logging from http import HTTPMethod -from typing import Optional, TypedDict +from typing import TypedDict import requests from werkzeug.datastructures import Headers @@ -20,16 +20,16 @@ class SimpleHttpRequest(TypedDict, total=False): method: HTTPMethod | str url: str - params: Optional[dict[str, str | list[str]]] + params: dict[str, str | list[str]] | None data: bytes - headers: Optional[dict[str, str]] - cookies: Optional[dict[str, str]] - timeout: Optional[int] - allow_redirects: Optional[bool] - stream: Optional[bool] - verify: Optional[bool] + headers: dict[str, str] | None + cookies: dict[str, str] | None + timeout: int | None + allow_redirects: bool | None + stream: bool | None + verify: bool | None # TODO: check if there was a situation where we'd pass certs? - cert: Optional[str | tuple[str, str]] + cert: str | tuple[str, str] | None class BaseRestApiHttpIntegration(RestApiIntegration): diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py index 6c0ca3245164b..9f670dcf12156 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/router.py @@ -33,6 +33,7 @@ class RouteHostPathParameters(TypedDict, total=False): server: str | None stage: str | None vpce_suffix: str | None + vpce_dns: str | None class ApiGatewayEndpoint: @@ -144,6 +145,13 @@ def create_not_found_response(api_id: str) -> Response: ) return not_found + def vpc_endpoint_handler( + self, request: Request, **kwargs: Unpack[RouteHostPathParameters] + ) -> Response: + # TODO validate the vpc endpoint exists in the account/region before routing + kwargs["api_id"] = request.headers.get("x-apigw-api-id") + return self.__call__(request, **kwargs) + class ApiGatewayRouter: router: Router[Handler] @@ -158,6 +166,9 @@ def __init__(self, router: Router[Handler] = None, handler: ApiGatewayEndpoint = def register_routes(self) -> None: LOG.debug("Registering API Gateway routes.") host_pattern = ".execute-api." + vpce_host_pattern = ( + ".execute-api.vpce." + ) deprecated_route_endpoint = deprecated_endpoint( endpoint=self.handler, previous_path="/restapis///_user_request_", @@ -214,6 +225,26 @@ def register_routes(self) -> None: endpoint=self.handler, strict_slashes=True, ), + self.router.add( + path="/", + host=vpce_host_pattern, + endpoint=self.handler.vpc_endpoint_handler, + defaults={"path": "", "stage": None}, + strict_slashes=True, + ), + self.router.add( + path="//", + host=vpce_host_pattern, + endpoint=self.handler.vpc_endpoint_handler, + defaults={"path": ""}, + strict_slashes=False, + ), + self.router.add( + path="//", + host=vpce_host_pattern, + endpoint=self.handler.vpc_endpoint_handler, + strict_slashes=True, + ), ] for rule in rules: self.registered_rules.append(rule) diff --git a/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py b/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py index e457c61180353..f51d6b6ab0b86 100644 --- a/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py +++ b/localstack-core/localstack/services/apigateway/next_gen/execute_api/variables.py @@ -1,4 +1,4 @@ -from typing import Optional, TypedDict +from typing import TypedDict class ContextVarsAuthorizer(TypedDict, total=False): @@ -6,9 +6,9 @@ class ContextVarsAuthorizer(TypedDict, total=False): # format # https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html - claims: Optional[dict[str, str]] + claims: dict[str, str] | None """Claims returned from the Amazon Cognito user pool after the method caller is successfully authenticated""" - principalId: Optional[str] + principalId: str | None """The principal user identification associated with the token sent by the client and returned from an API Gateway Lambda authorizer""" @@ -29,38 +29,38 @@ class ContextVarsIdentityClientCert(TypedDict, total=False): class ContextVarsIdentity(TypedDict, total=False): # https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html - accountId: Optional[str] + accountId: str | None """The AWS account ID associated with the request.""" - accessKey: Optional[str] + accessKey: str | None """The AWS access key associated with the request.""" - apiKey: Optional[str] + apiKey: str | None """For API methods that require an API key, this variable is the API key associated with the method request.""" - apiKeyId: Optional[str] + apiKeyId: str | None """The API key ID associated with an API request that requires an API key.""" - caller: Optional[str] + caller: str | None """The principal identifier of the caller that signed the request. Supported for resources that use IAM authorization.""" - cognitoAuthenticationProvider: Optional[str] + cognitoAuthenticationProvider: str | None """A comma-separated list of the Amazon Cognito authentication providers used by the caller making the request""" - cognitoAuthenticationType: Optional[str] + cognitoAuthenticationType: str | None """The Amazon Cognito authentication type of the caller making the request""" - cognitoIdentityId: Optional[str] + cognitoIdentityId: str | None """The Amazon Cognito identity ID of the caller making the request""" - cognitoIdentityPoolId: Optional[str] + cognitoIdentityPoolId: str | None """The Amazon Cognito identity pool ID of the caller making the request""" - principalOrgId: Optional[str] + principalOrgId: str | None """The AWS organization ID.""" - sourceIp: Optional[str] + sourceIp: str | None """The source IP address of the immediate TCP connection making the request to the API Gateway endpoint""" clientCert: ContextVarsIdentityClientCert - vpcId: Optional[str] + vpcId: str | None """The VPC ID of the VPC making the request to the API Gateway endpoint.""" - vpceId: Optional[str] + vpceId: str | None """The VPC endpoint ID of the VPC endpoint making the request to the API Gateway endpoint.""" - user: Optional[str] + user: str | None """The principal identifier of the user that will be authorized against resource access for resources that use IAM authorization.""" - userAgent: Optional[str] + userAgent: str | None """The User-Agent header of the API caller.""" - userArn: Optional[str] + userArn: str | None """The Amazon Resource Name (ARN) of the effective user identified after authentication.""" @@ -95,9 +95,9 @@ class ContextVariables(TypedDict, total=False): """The API owner's AWS account ID.""" apiId: str """The identifier API Gateway assigns to your API.""" - authorizer: Optional[ContextVarsAuthorizer] + authorizer: ContextVarsAuthorizer | None """The principal user identification associated with the token.""" - awsEndpointRequestId: Optional[str] + awsEndpointRequestId: str | None """The AWS endpoint's request ID.""" deploymentId: str """The ID of the API deployment.""" @@ -111,8 +111,8 @@ class ContextVariables(TypedDict, total=False): """The extended ID that API Gateway generates and assigns to the API request. """ httpMethod: str """The HTTP method used""" - identity: Optional[ContextVarsIdentity] - isCanaryRequest: Optional[bool] + identity: ContextVarsIdentity | None + isCanaryRequest: bool | None """Indicates if the request was directed to the canary""" path: str """The request path.""" @@ -120,76 +120,76 @@ class ContextVariables(TypedDict, total=False): """The request protocol""" requestId: str """An ID for the request. Clients can override this request ID. """ - requestOverride: Optional[ContextVarsRequestOverride] + requestOverride: ContextVarsRequestOverride | None """Request override. Only exists for request mapping template""" requestTime: str """The CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm).""" requestTimeEpoch: int """The Epoch-formatted request time, in milliseconds.""" - resourceId: Optional[str] + resourceId: str | None """The identifier that API Gateway assigns to your resource.""" - resourcePath: Optional[str] + resourcePath: str | None """The path to your resource""" - responseOverride: Optional[ContextVarsResponseOverride] + responseOverride: ContextVarsResponseOverride | None """Response override. Only exists for response mapping template""" stage: str """The deployment stage of the API request """ - wafResponseCode: Optional[str] + wafResponseCode: str | None """The response received from AWS WAF: WAF_ALLOW or WAF_BLOCK. Will not be set if the stage is not associated with a web ACL""" - webaclArn: Optional[str] + webaclArn: str | None """The complete ARN of the web ACL that is used to decide whether to allow or block the request. Will not be set if the stage is not associated with a web ACL.""" class LoggingContextVarsAuthorize(TypedDict, total=False): - error: Optional[str] - latency: Optional[str] - status: Optional[str] + error: str | None + latency: str | None + status: str | None class LoggingContextVarsAuthorizer(TypedDict, total=False): - error: Optional[str] - integrationLatency: Optional[str] - integrationStatus: Optional[str] - latency: Optional[str] - requestId: Optional[str] - status: Optional[str] + error: str | None + integrationLatency: str | None + integrationStatus: str | None + latency: str | None + requestId: str | None + status: str | None class LoggingContextVarsAuthenticate(TypedDict, total=False): - error: Optional[str] - latency: Optional[str] - status: Optional[str] + error: str | None + latency: str | None + status: str | None class LoggingContextVarsCustomDomain(TypedDict, total=False): - basePathMatched: Optional[str] + basePathMatched: str | None class LoggingContextVarsIntegration(TypedDict, total=False): - error: Optional[str] - integrationStatus: Optional[str] - latency: Optional[str] - requestId: Optional[str] - status: Optional[str] + error: str | None + integrationStatus: str | None + latency: str | None + requestId: str | None + status: str | None class LoggingContextVarsWaf(TypedDict, total=False): - error: Optional[str] - latency: Optional[str] - status: Optional[str] + error: str | None + latency: str | None + status: str | None class LoggingContextVariables(TypedDict, total=False): - authorize: Optional[LoggingContextVarsAuthorize] - authorizer: Optional[LoggingContextVarsAuthorizer] - authenticate: Optional[LoggingContextVarsAuthenticate] - customDomain: Optional[LoggingContextVarsCustomDomain] - endpointType: Optional[str] - integration: Optional[LoggingContextVarsIntegration] - integrationLatency: Optional[str] - integrationStatus: Optional[str] - responseLatency: Optional[str] - responseLength: Optional[str] - status: Optional[str] - waf: Optional[LoggingContextVarsWaf] - xrayTraceId: Optional[str] + authorize: LoggingContextVarsAuthorize | None + authorizer: LoggingContextVarsAuthorizer | None + authenticate: LoggingContextVarsAuthenticate | None + customDomain: LoggingContextVarsCustomDomain | None + endpointType: str | None + integration: LoggingContextVarsIntegration | None + integrationLatency: str | None + integrationStatus: str | None + responseLatency: str | None + responseLength: str | None + status: str | None + waf: LoggingContextVarsWaf | None + xrayTraceId: str | None diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py index 8c78925a5a8b8..40974b41688a9 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,8 +14,8 @@ class ApiGatewayAccountProperties(TypedDict): - CloudWatchRoleArn: Optional[str] - Id: Optional[str] + CloudWatchRoleArn: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py index d7dc5c91ce0d1..3fa69d04da2f1 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_account_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayAccountProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Account" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_account import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py index 1385cd6c5d01c..c1d99abec70be 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,25 +15,25 @@ class ApiGatewayApiKeyProperties(TypedDict): - APIKeyId: Optional[str] - CustomerId: Optional[str] - Description: Optional[str] - Enabled: Optional[bool] - GenerateDistinctId: Optional[bool] - Name: Optional[str] - StageKeys: Optional[list[StageKey]] - Tags: Optional[list[Tag]] - Value: Optional[str] + APIKeyId: str | None + CustomerId: str | None + Description: str | None + Enabled: bool | None + GenerateDistinctId: bool | None + Name: str | None + StageKeys: list[StageKey] | None + Tags: list[Tag] | None + Value: str | None class StageKey(TypedDict): - RestApiId: Optional[str] - StageName: Optional[str] + RestApiId: str | None + StageName: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py index 352ec19eec4d3..63243f3901f46 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_apikey_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayApiKeyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::ApiKey" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_apikey import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py index 51debd7811631..8b9378d53f7f0 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class ApiGatewayBasePathMappingProperties(TypedDict): - DomainName: Optional[str] - BasePath: Optional[str] - RestApiId: Optional[str] - Stage: Optional[str] + DomainName: str | None + BasePath: str | None + RestApiId: str | None + Stage: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py index 2dcb4b036e9ef..7e02201f7df56 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_basepathmapping_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayBasePathMappingProviderPlugin(CloudFormationResourceProviderPlug name = "AWS::ApiGateway::BasePathMapping" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_basepathmapping import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py index 97536ea2befc2..293440f594802 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,69 +15,69 @@ class ApiGatewayDeploymentProperties(TypedDict): - RestApiId: Optional[str] - DeploymentCanarySettings: Optional[DeploymentCanarySettings] - DeploymentId: Optional[str] - Description: Optional[str] - StageDescription: Optional[StageDescription] - StageName: Optional[str] + RestApiId: str | None + DeploymentCanarySettings: DeploymentCanarySettings | None + DeploymentId: str | None + Description: str | None + StageDescription: StageDescription | None + StageName: str | None class DeploymentCanarySettings(TypedDict): - PercentTraffic: Optional[float] - StageVariableOverrides: Optional[dict] - UseStageCache: Optional[bool] + PercentTraffic: float | None + StageVariableOverrides: dict | None + UseStageCache: bool | None class AccessLogSetting(TypedDict): - DestinationArn: Optional[str] - Format: Optional[str] + DestinationArn: str | None + Format: str | None class CanarySetting(TypedDict): - PercentTraffic: Optional[float] - StageVariableOverrides: Optional[dict] - UseStageCache: Optional[bool] + PercentTraffic: float | None + StageVariableOverrides: dict | None + UseStageCache: bool | None class MethodSetting(TypedDict): - CacheDataEncrypted: Optional[bool] - CacheTtlInSeconds: Optional[int] - CachingEnabled: Optional[bool] - DataTraceEnabled: Optional[bool] - HttpMethod: Optional[str] - LoggingLevel: Optional[str] - MetricsEnabled: Optional[bool] - ResourcePath: Optional[str] - ThrottlingBurstLimit: Optional[int] - ThrottlingRateLimit: Optional[float] + CacheDataEncrypted: bool | None + CacheTtlInSeconds: int | None + CachingEnabled: bool | None + DataTraceEnabled: bool | None + HttpMethod: str | None + LoggingLevel: str | None + MetricsEnabled: bool | None + ResourcePath: str | None + ThrottlingBurstLimit: int | None + ThrottlingRateLimit: float | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class StageDescription(TypedDict): - AccessLogSetting: Optional[AccessLogSetting] - CacheClusterEnabled: Optional[bool] - CacheClusterSize: Optional[str] - CacheDataEncrypted: Optional[bool] - CacheTtlInSeconds: Optional[int] - CachingEnabled: Optional[bool] - CanarySetting: Optional[CanarySetting] - ClientCertificateId: Optional[str] - DataTraceEnabled: Optional[bool] - Description: Optional[str] - DocumentationVersion: Optional[str] - LoggingLevel: Optional[str] - MethodSettings: Optional[list[MethodSetting]] - MetricsEnabled: Optional[bool] - Tags: Optional[list[Tag]] - ThrottlingBurstLimit: Optional[int] - ThrottlingRateLimit: Optional[float] - TracingEnabled: Optional[bool] - Variables: Optional[dict] + AccessLogSetting: AccessLogSetting | None + CacheClusterEnabled: bool | None + CacheClusterSize: str | None + CacheDataEncrypted: bool | None + CacheTtlInSeconds: int | None + CachingEnabled: bool | None + CanarySetting: CanarySetting | None + ClientCertificateId: str | None + DataTraceEnabled: bool | None + Description: str | None + DocumentationVersion: str | None + LoggingLevel: str | None + MethodSettings: list[MethodSetting] | None + MetricsEnabled: bool | None + Tags: list[Tag] | None + ThrottlingBurstLimit: int | None + ThrottlingRateLimit: float | None + TracingEnabled: bool | None + Variables: dict | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py index 80ff9801a1ed5..3efdea755c554 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_deployment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayDeploymentProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Deployment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_deployment import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py index 778ec9da3cbf8..74f7be6e9602a 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,32 +15,32 @@ class ApiGatewayDomainNameProperties(TypedDict): - CertificateArn: Optional[str] - DistributionDomainName: Optional[str] - DistributionHostedZoneId: Optional[str] - DomainName: Optional[str] - EndpointConfiguration: Optional[EndpointConfiguration] - MutualTlsAuthentication: Optional[MutualTlsAuthentication] - OwnershipVerificationCertificateArn: Optional[str] - RegionalCertificateArn: Optional[str] - RegionalDomainName: Optional[str] - RegionalHostedZoneId: Optional[str] - SecurityPolicy: Optional[str] - Tags: Optional[list[Tag]] + CertificateArn: str | None + DistributionDomainName: str | None + DistributionHostedZoneId: str | None + DomainName: str | None + EndpointConfiguration: EndpointConfiguration | None + MutualTlsAuthentication: MutualTlsAuthentication | None + OwnershipVerificationCertificateArn: str | None + RegionalCertificateArn: str | None + RegionalDomainName: str | None + RegionalHostedZoneId: str | None + SecurityPolicy: str | None + Tags: list[Tag] | None class EndpointConfiguration(TypedDict): - Types: Optional[list[str]] + Types: list[str] | None class MutualTlsAuthentication(TypedDict): - TruststoreUri: Optional[str] - TruststoreVersion: Optional[str] + TruststoreUri: str | None + TruststoreVersion: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py index 49e6db22f12d8..ed8a86494b681 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_domainname_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayDomainNameProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::DomainName" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_domainname import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py index bb52d43256e7b..963a907be3d58 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,12 +15,12 @@ class ApiGatewayGatewayResponseProperties(TypedDict): - ResponseType: Optional[str] - RestApiId: Optional[str] - Id: Optional[str] - ResponseParameters: Optional[dict] - ResponseTemplates: Optional[dict] - StatusCode: Optional[str] + ResponseType: str | None + RestApiId: str | None + Id: str | None + ResponseParameters: dict | None + ResponseTemplates: dict | None + StatusCode: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py index 86f43d46cdd21..55113f1f23533 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_gatewayresponse_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayGatewayResponseProviderPlugin(CloudFormationResourceProviderPlug name = "AWS::ApiGateway::GatewayResponse" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_gatewayresponse import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py index 64598a4463898..752fbbde53fe4 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method.py @@ -3,7 +3,7 @@ from copy import deepcopy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,50 +15,50 @@ class ApiGatewayMethodProperties(TypedDict): - HttpMethod: Optional[str] - ResourceId: Optional[str] - RestApiId: Optional[str] - ApiKeyRequired: Optional[bool] - AuthorizationScopes: Optional[list[str]] - AuthorizationType: Optional[str] - AuthorizerId: Optional[str] - Integration: Optional[Integration] - MethodResponses: Optional[list[MethodResponse]] - OperationName: Optional[str] - RequestModels: Optional[dict] - RequestParameters: Optional[dict] - RequestValidatorId: Optional[str] + HttpMethod: str | None + ResourceId: str | None + RestApiId: str | None + ApiKeyRequired: bool | None + AuthorizationScopes: list[str] | None + AuthorizationType: str | None + AuthorizerId: str | None + Integration: Integration | None + MethodResponses: list[MethodResponse] | None + OperationName: str | None + RequestModels: dict | None + RequestParameters: dict | None + RequestValidatorId: str | None class IntegrationResponse(TypedDict): - StatusCode: Optional[str] - ContentHandling: Optional[str] - ResponseParameters: Optional[dict] - ResponseTemplates: Optional[dict] - SelectionPattern: Optional[str] + StatusCode: str | None + ContentHandling: str | None + ResponseParameters: dict | None + ResponseTemplates: dict | None + SelectionPattern: str | None class Integration(TypedDict): - Type: Optional[str] - CacheKeyParameters: Optional[list[str]] - CacheNamespace: Optional[str] - ConnectionId: Optional[str] - ConnectionType: Optional[str] - ContentHandling: Optional[str] - Credentials: Optional[str] - IntegrationHttpMethod: Optional[str] - IntegrationResponses: Optional[list[IntegrationResponse]] - PassthroughBehavior: Optional[str] - RequestParameters: Optional[dict] - RequestTemplates: Optional[dict] - TimeoutInMillis: Optional[int] - Uri: Optional[str] + Type: str | None + CacheKeyParameters: list[str] | None + CacheNamespace: str | None + ConnectionId: str | None + ConnectionType: str | None + ContentHandling: str | None + Credentials: str | None + IntegrationHttpMethod: str | None + IntegrationResponses: list[IntegrationResponse] | None + PassthroughBehavior: str | None + RequestParameters: dict | None + RequestTemplates: dict | None + TimeoutInMillis: int | None + Uri: str | None class MethodResponse(TypedDict): - StatusCode: Optional[str] - ResponseModels: Optional[dict] - ResponseParameters: Optional[dict] + StatusCode: str | None + ResponseModels: dict | None + ResponseParameters: dict | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py index 34e0cec7971a9..78fcefff25ad5 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_method_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayMethodProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Method" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_method import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py index 07883e62983ca..4a431fcef79fc 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,11 +15,11 @@ class ApiGatewayModelProperties(TypedDict): - RestApiId: Optional[str] - ContentType: Optional[str] - Description: Optional[str] - Name: Optional[str] - Schema: Optional[dict | str] + RestApiId: str | None + ContentType: str | None + Description: str | None + Name: str | None + Schema: dict | str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py index d1bd727b602e5..4396c861c7e30 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_model_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayModelProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Model" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_model import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py index 55d2a3bc4964e..03e0f7371100e 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,11 +14,11 @@ class ApiGatewayRequestValidatorProperties(TypedDict): - RestApiId: Optional[str] - Name: Optional[str] - RequestValidatorId: Optional[str] - ValidateRequestBody: Optional[bool] - ValidateRequestParameters: Optional[bool] + RestApiId: str | None + Name: str | None + RequestValidatorId: str | None + ValidateRequestBody: bool | None + ValidateRequestParameters: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py index 41175341a69de..7dc730bc55e1a 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_requestvalidator_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayRequestValidatorProviderPlugin(CloudFormationResourceProviderPlu name = "AWS::ApiGateway::RequestValidator" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_requestvalidator import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py index 89b868306e68d..dc110e242e02a 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -17,10 +17,10 @@ class ApiGatewayResourceProperties(TypedDict): - ParentId: Optional[str] - PathPart: Optional[str] - RestApiId: Optional[str] - ResourceId: Optional[str] + ParentId: str | None + PathPart: str | None + RestApiId: str | None + ResourceId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py index f7ece7204435d..8d37d2cad25ed 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_resource_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayResourceProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Resource" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_resource import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py index c90e2b36f328b..9371d5acb09a9 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,40 +17,40 @@ class ApiGatewayRestApiProperties(TypedDict): - ApiKeySourceType: Optional[str] - BinaryMediaTypes: Optional[list[str]] - Body: Optional[dict | str] - BodyS3Location: Optional[S3Location] - CloneFrom: Optional[str] - Description: Optional[str] - DisableExecuteApiEndpoint: Optional[bool] - EndpointConfiguration: Optional[EndpointConfiguration] - FailOnWarnings: Optional[bool] - MinimumCompressionSize: Optional[int] - Mode: Optional[str] - Name: Optional[str] - Parameters: Optional[dict | str] - Policy: Optional[dict | str] - RestApiId: Optional[str] - RootResourceId: Optional[str] - Tags: Optional[list[Tag]] + ApiKeySourceType: str | None + BinaryMediaTypes: list[str] | None + Body: dict | str | None + BodyS3Location: S3Location | None + CloneFrom: str | None + Description: str | None + DisableExecuteApiEndpoint: bool | None + EndpointConfiguration: EndpointConfiguration | None + FailOnWarnings: bool | None + MinimumCompressionSize: int | None + Mode: str | None + Name: str | None + Parameters: dict | str | None + Policy: dict | str | None + RestApiId: str | None + RootResourceId: str | None + Tags: list[Tag] | None class S3Location(TypedDict): - Bucket: Optional[str] - ETag: Optional[str] - Key: Optional[str] - Version: Optional[str] + Bucket: str | None + ETag: str | None + Key: str | None + Version: str | None class EndpointConfiguration(TypedDict): - Types: Optional[list[str]] - VpcEndpointIds: Optional[list[str]] + Types: list[str] | None + VpcEndpointIds: list[str] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py index e53c4a4d8205f..044badf1b3426 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_restapi_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayRestApiProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::RestApi" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_restapi import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py index b2b98bc715455..2c9fccbc051f4 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage.py @@ -3,7 +3,7 @@ import copy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,50 +16,50 @@ class ApiGatewayStageProperties(TypedDict): - RestApiId: Optional[str] - AccessLogSetting: Optional[AccessLogSetting] - CacheClusterEnabled: Optional[bool] - CacheClusterSize: Optional[str] - CanarySetting: Optional[CanarySetting] - ClientCertificateId: Optional[str] - DeploymentId: Optional[str] - Description: Optional[str] - DocumentationVersion: Optional[str] - MethodSettings: Optional[list[MethodSetting]] - StageName: Optional[str] - Tags: Optional[list[Tag]] - TracingEnabled: Optional[bool] - Variables: Optional[dict] + RestApiId: str | None + AccessLogSetting: AccessLogSetting | None + CacheClusterEnabled: bool | None + CacheClusterSize: str | None + CanarySetting: CanarySetting | None + ClientCertificateId: str | None + DeploymentId: str | None + Description: str | None + DocumentationVersion: str | None + MethodSettings: list[MethodSetting] | None + StageName: str | None + Tags: list[Tag] | None + TracingEnabled: bool | None + Variables: dict | None class AccessLogSetting(TypedDict): - DestinationArn: Optional[str] - Format: Optional[str] + DestinationArn: str | None + Format: str | None class CanarySetting(TypedDict): - DeploymentId: Optional[str] - PercentTraffic: Optional[float] - StageVariableOverrides: Optional[dict] - UseStageCache: Optional[bool] + DeploymentId: str | None + PercentTraffic: float | None + StageVariableOverrides: dict | None + UseStageCache: bool | None class MethodSetting(TypedDict): - CacheDataEncrypted: Optional[bool] - CacheTtlInSeconds: Optional[int] - CachingEnabled: Optional[bool] - DataTraceEnabled: Optional[bool] - HttpMethod: Optional[str] - LoggingLevel: Optional[str] - MetricsEnabled: Optional[bool] - ResourcePath: Optional[str] - ThrottlingBurstLimit: Optional[int] - ThrottlingRateLimit: Optional[float] + CacheDataEncrypted: bool | None + CacheTtlInSeconds: int | None + CachingEnabled: bool | None + DataTraceEnabled: bool | None + HttpMethod: str | None + LoggingLevel: str | None + MetricsEnabled: bool | None + ResourcePath: str | None + ThrottlingBurstLimit: int | None + ThrottlingRateLimit: float | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py index e0898bae2c695..58a9c1236bec7 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_stage_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayStageProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::Stage" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_stage import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py index 1e10c9badfc3f..6ac6a0d40edc6 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -18,35 +18,35 @@ class ApiGatewayUsagePlanProperties(TypedDict): - ApiStages: Optional[list[ApiStage]] - Description: Optional[str] - Id: Optional[str] - Quota: Optional[QuotaSettings] - Tags: Optional[list[Tag]] - Throttle: Optional[ThrottleSettings] - UsagePlanName: Optional[str] + ApiStages: list[ApiStage] | None + Description: str | None + Id: str | None + Quota: QuotaSettings | None + Tags: list[Tag] | None + Throttle: ThrottleSettings | None + UsagePlanName: str | None class ApiStage(TypedDict): - ApiId: Optional[str] - Stage: Optional[str] - Throttle: Optional[dict] + ApiId: str | None + Stage: str | None + Throttle: dict | None class QuotaSettings(TypedDict): - Limit: Optional[int] - Offset: Optional[int] - Period: Optional[str] + Limit: int | None + Offset: int | None + Period: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ThrottleSettings(TypedDict): - BurstLimit: Optional[int] - RateLimit: Optional[float] + BurstLimit: int | None + RateLimit: float | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py index 154207ac69b58..531f507adf929 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplan_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayUsagePlanProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ApiGateway::UsagePlan" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_usageplan import ( diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py index 33a6e155d5c4f..1ce7395510918 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,10 +15,10 @@ class ApiGatewayUsagePlanKeyProperties(TypedDict): - KeyId: Optional[str] - KeyType: Optional[str] - UsagePlanId: Optional[str] - Id: Optional[str] + KeyId: str | None + KeyType: str | None + UsagePlanId: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py index eb21b610bfc22..8bd408b166123 100644 --- a/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py +++ b/localstack-core/localstack/services/apigateway/resource_providers/aws_apigateway_usageplankey_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ApiGatewayUsagePlanKeyProviderPlugin(CloudFormationResourceProviderPlugin) name = "AWS::ApiGateway::UsagePlanKey" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.apigateway.resource_providers.aws_apigateway_usageplankey import ( diff --git a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py index 7e5eb5ca2f988..e2b221461ab36 100644 --- a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py +++ b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,7 +14,7 @@ class CDKMetadataProperties(TypedDict): - Id: Optional[str] + Id: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -83,8 +83,9 @@ def update( """ model = request.desired_state + result_model = {**model, "Id": request.previous_state["Id"]} return ProgressEvent( status=OperationStatus.SUCCESS, - resource_model=model, + resource_model=result_model, ) diff --git a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py index 924ca3cb79eae..e8d40a6c9e3e5 100644 --- a/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py +++ b/localstack-core/localstack/services/cdk/resource_providers/cdk_metadata_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaAliasProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CDK::Metadata" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cdk.resource_providers.cdk_metadata import CDKMetadataProvider diff --git a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py index d79d62975e87f..1ce68c51aab2d 100644 --- a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py +++ b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,25 +14,25 @@ class CertificateManagerCertificateProperties(TypedDict): - DomainName: Optional[str] - CertificateAuthorityArn: Optional[str] - CertificateTransparencyLoggingPreference: Optional[str] - DomainValidationOptions: Optional[list[DomainValidationOption]] - Id: Optional[str] - SubjectAlternativeNames: Optional[list[str]] - Tags: Optional[list[Tag]] - ValidationMethod: Optional[str] + DomainName: str | None + CertificateAuthorityArn: str | None + CertificateTransparencyLoggingPreference: str | None + DomainValidationOptions: list[DomainValidationOption] | None + Id: str | None + SubjectAlternativeNames: list[str] | None + Tags: list[Tag] | None + ValidationMethod: str | None class DomainValidationOption(TypedDict): - DomainName: Optional[str] - HostedZoneId: Optional[str] - ValidationDomain: Optional[str] + DomainName: str | None + HostedZoneId: str | None + ValidationDomain: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py index 5aae4de01c7b3..134a953c2dd21 100644 --- a/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py +++ b/localstack-core/localstack/services/certificatemanager/resource_providers/aws_certificatemanager_certificate_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CertificateManagerCertificateProviderPlugin(CloudFormationResourceProvider name = "AWS::CertificateManager::Certificate" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.certificatemanager.resource_providers.aws_certificatemanager_certificate import ( diff --git a/localstack-core/localstack/services/cloudformation/cfn_utils.py b/localstack-core/localstack/services/cloudformation/cfn_utils.py index 6fcc5d16fb573..052df4d91ded2 100644 --- a/localstack-core/localstack/services/cloudformation/cfn_utils.py +++ b/localstack-core/localstack/services/cloudformation/cfn_utils.py @@ -1,5 +1,5 @@ import json -from typing import Callable +from collections.abc import Callable from localstack.utils.objects import recurse_object diff --git a/localstack-core/localstack/services/cloudformation/deployment_utils.py b/localstack-core/localstack/services/cloudformation/deployment_utils.py index 6355db6b5c27a..771b2ac6bf724 100644 --- a/localstack-core/localstack/services/cloudformation/deployment_utils.py +++ b/localstack-core/localstack/services/cloudformation/deployment_utils.py @@ -2,8 +2,8 @@ import json import logging import re +from collections.abc import Callable from copy import deepcopy -from typing import Callable, List from localstack import config from localstack.utils import common @@ -88,7 +88,7 @@ def do_replace(account_id: str, region_name: str, params, logical_resource_id, * return do_replace -def lambda_keys_to_lower(key=None, skip_children_of: List[str] = None): +def lambda_keys_to_lower(key=None, skip_children_of: list[str] = None): return ( lambda account_id, region_name, @@ -224,7 +224,7 @@ def fix_boto_parameters_based_on_report(original_params: dict, report: str) -> d cast_class = getattr(builtins, valid_class) old_value = get_nested(params, param_name) - if cast_class == bool and str(old_value).lower() in ["true", "false"]: + if isinstance(cast_class, bool) and str(old_value).lower() in ["true", "false"]: new_value = str(old_value).lower() == "true" else: new_value = cast_class(old_value) @@ -252,15 +252,17 @@ def convert_data_types(type_conversions: dict[str, Callable], params: dict) -> d attr_names = type_conversions.keys() or [] def cast(_obj, _type): - if _type == bool: - return _obj in ["True", "true", True] - if _type == str: - if isinstance(_obj, bool): - return str(_obj).lower() - return str(_obj) - if _type in (int, float): - return _type(_obj) - return _obj + match _type: + case builtins.bool: + return _obj in ["True", "true", True] + case builtins.str: + if isinstance(_obj, bool): + return str(_obj).lower() + return str(_obj) + case builtins.int | builtins.float: + return _type(_obj) + case _: + return _obj def fix_types(o, **kwargs): if isinstance(o, dict): diff --git a/localstack-core/localstack/services/cloudformation/engine/changes.py b/localstack-core/localstack/services/cloudformation/engine/changes.py index ae6ced9e5563e..2a3f1ed8347d0 100644 --- a/localstack-core/localstack/services/cloudformation/engine/changes.py +++ b/localstack-core/localstack/services/cloudformation/engine/changes.py @@ -1,4 +1,4 @@ -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict Action = str @@ -6,11 +6,11 @@ class ResourceChange(TypedDict): Action: Action LogicalResourceId: str - PhysicalResourceId: Optional[str] + PhysicalResourceId: str | None ResourceType: str Scope: list Details: list - Replacement: Optional[Literal["False"]] + Replacement: Literal["False"] | None class ChangeConfig(TypedDict): diff --git a/localstack-core/localstack/services/cloudformation/engine/entities.py b/localstack-core/localstack/services/cloudformation/engine/entities.py index e1498258694ee..71265370eeb25 100644 --- a/localstack-core/localstack/services/cloudformation/engine/entities.py +++ b/localstack-core/localstack/services/cloudformation/engine/entities.py @@ -1,5 +1,5 @@ import logging -from typing import Optional, TypedDict +from typing import TypedDict from localstack.aws.api.cloudformation import Capability, ChangeSetType, Parameter from localstack.services.cloudformation.engine.parameters import ( @@ -52,14 +52,14 @@ def __init__(self, metadata: dict): class CreateChangeSetInput(TypedDict): StackName: str Capabilities: list[Capability] - ChangeSetName: Optional[str] - ChangSetType: Optional[ChangeSetType] + ChangeSetName: str | None + ChangSetType: ChangeSetType | None Parameters: list[Parameter] class StackTemplate(TypedDict): StackName: str - ChangeSetName: Optional[str] + ChangeSetName: str | None Outputs: dict Resources: dict @@ -83,9 +83,9 @@ def __init__( self, account_id: str, region_name: str, - metadata: Optional[CreateChangeSetInput] = None, - template: Optional[StackTemplate] = None, - template_body: Optional[str] = None, + metadata: CreateChangeSetInput | None = None, + template: StackTemplate | None = None, + template_body: str | None = None, ): self.account_id = account_id self.region_name = region_name @@ -93,7 +93,7 @@ def __init__( if template is None: template = {} - self.resolved_outputs = list() # TODO + self.resolved_outputs = [] # TODO self.resolved_parameters: dict[str, StackParameter] = {} self.resolved_conditions: dict[str, bool] = {} @@ -185,7 +185,7 @@ def describe_details(self): result.setdefault(attr, []) return result - def set_stack_status(self, status: str, status_reason: Optional[str] = None): + def set_stack_status(self, status: str, status_reason: str | None = None): self.metadata["StackStatus"] = status if "FAILED" in status: self.metadata["StackStatusReason"] = status_reason or "Deployment failed" @@ -428,10 +428,10 @@ def changes(self): # V2 only def populate_update_graph( self, - before_template: Optional[dict], - after_template: Optional[dict], - before_parameters: Optional[dict], - after_parameters: Optional[dict], + before_template: dict | None, + after_template: dict | None, + before_parameters: dict | None, + after_parameters: dict | None, ) -> None: change_set_model = ChangeSetModel( before_template=before_template, diff --git a/localstack-core/localstack/services/cloudformation/engine/parameters.py b/localstack-core/localstack/services/cloudformation/engine/parameters.py index ba39fafc40db2..024c5e4a54150 100644 --- a/localstack-core/localstack/services/cloudformation/engine/parameters.py +++ b/localstack-core/localstack/services/cloudformation/engine/parameters.py @@ -19,7 +19,7 @@ """ import logging -from typing import Literal, Optional, TypedDict +from typing import Literal, TypedDict from botocore.exceptions import ClientError @@ -76,7 +76,7 @@ def resolve_parameters( :param old_parameters: The old parameters from the previous stack deployment, if available :return: a copy of new_parameters with resolved values """ - resolved_parameters = dict() + resolved_parameters = {} # populate values for every parameter declared in the template for pm in parameter_declarations.values(): @@ -179,8 +179,8 @@ def convert_stack_parameters_to_dict(in_params: list[Parameter] | None) -> dict[ class LegacyParameterProperties(TypedDict): Value: str ParameterType: str - ParameterValue: Optional[str] - ResolvedValue: Optional[str] + ParameterValue: str | None + ResolvedValue: str | None class LegacyParameter(TypedDict): diff --git a/localstack-core/localstack/services/cloudformation/engine/template_deployer.py b/localstack-core/localstack/services/cloudformation/engine/template_deployer.py index e3a0802c54bed..6e865380b84c3 100644 --- a/localstack-core/localstack/services/cloudformation/engine/template_deployer.py +++ b/localstack-core/localstack/services/cloudformation/engine/template_deployer.py @@ -4,7 +4,6 @@ import re import traceback import uuid -from typing import Optional from botocore.exceptions import ClientError @@ -86,7 +85,7 @@ def get_attr_from_model_instance( attribute_name: str, resource_type: str, resource_id: str, - attribute_sub_name: Optional[str] = None, + attribute_sub_name: str | None = None, ) -> str: if resource["PhysicalResourceId"] == MOCK_REFERENCE: LOG.warning( @@ -1029,7 +1028,7 @@ def init_resource_status(self, resources=None, stack=None, action="CREATE"): stack.set_resource_status(resource_id, f"{action}_IN_PROGRESS") def get_change_config( - self, action: str, resource: dict, change_set_id: Optional[str] = None + self, action: str, resource: dict, change_set_id: str | None = None ) -> ChangeConfig: result = ChangeConfig( **{ @@ -1105,10 +1104,10 @@ def construct_changes( existing_stack, new_stack, # TODO: remove initialize argument from here, and determine action based on resource status - initialize: Optional[bool] = False, + initialize: bool | None = False, change_set_id=None, - append_to_changeset: Optional[bool] = False, - filter_unchanged_resources: Optional[bool] = False, + append_to_changeset: bool | None = False, + filter_unchanged_resources: bool | None = False, ) -> list[ChangeConfig]: old_resources = existing_stack.template["Resources"] new_resources = new_stack.template["Resources"] @@ -1140,9 +1139,9 @@ def apply_changes( self, existing_stack: Stack, new_stack: StackChangeSet, - change_set_id: Optional[str] = None, - initialize: Optional[bool] = False, - action: Optional[str] = None, + change_set_id: str | None = None, + initialize: bool | None = False, + action: str | None = None, ): old_resources = existing_stack.template["Resources"] new_resources = new_stack.template["Resources"] @@ -1201,7 +1200,7 @@ def apply_changes_in_loop( self, changes: list[ChangeConfig], stack: Stack, - action: Optional[str] = None, + action: str | None = None, new_stack=None, ): def _run(*args): diff --git a/localstack-core/localstack/services/cloudformation/engine/template_utils.py b/localstack-core/localstack/services/cloudformation/engine/template_utils.py index 062e4a3f1f840..c156e22e9cfcb 100644 --- a/localstack-core/localstack/services/cloudformation/engine/template_utils.py +++ b/localstack-core/localstack/services/cloudformation/engine/template_utils.py @@ -322,18 +322,40 @@ def resolve_condition( case "Fn::Select": index = v[0] options = v[1] - for i, option in enumerate(options): - if isinstance(option, dict): - options[i] = resolve_condition( - account_id, - region_name, - option, - conditions, - parameters, - mappings, - stack_name, - ) - return options[index] + + if isinstance(options, dict): + options = resolve_condition( + account_id, + region_name, + options, + conditions, + parameters, + mappings, + stack_name, + ) + + if isinstance(options, list): + for i, option in enumerate(options): + if isinstance(option, dict): + options[i] = resolve_condition( + account_id, + region_name, + option, + conditions, + parameters, + mappings, + stack_name, + ) + + return options[index] + + if index != 0: + raise Exception( + f"Template error: Fn::Select cannot select nonexistent value at index {index}" + ) + + return options + case "Fn::Sub": # we can assume anything in there is a ref if isinstance(v, str): diff --git a/localstack-core/localstack/services/cloudformation/engine/transformers.py b/localstack-core/localstack/services/cloudformation/engine/transformers.py index 1518750fb1bc7..a77665703c3f2 100644 --- a/localstack-core/localstack/services/cloudformation/engine/transformers.py +++ b/localstack-core/localstack/services/cloudformation/engine/transformers.py @@ -3,9 +3,10 @@ import logging import os import re +from collections.abc import Callable from copy import deepcopy from dataclasses import dataclass -from typing import Any, Callable, Dict, Optional, Type, Union +from typing import Any import boto3 from botocore.exceptions import ClientError @@ -27,7 +28,7 @@ EXTENSIONS_TRANSFORM = "AWS::LanguageExtensions" SECRETSMANAGER_TRANSFORM = "AWS::SecretsManager-2020-07-23" -TransformResult = Union[dict, str] +TransformResult = dict | str @dataclass @@ -83,7 +84,7 @@ def transform(self, account_id: str, region_name: str, parameters: dict) -> Tran # maps transformer names to implementing classes -transformers: Dict[str, Type] = {"AWS::Include": AwsIncludeTransformer} +transformers: dict[str, type] = {"AWS::Include": AwsIncludeTransformer} def apply_intrinsic_transformations( @@ -449,7 +450,7 @@ def _visit(obj: Any, path: Any): def apply_serverless_transformation( account_id: str, region_name: str, parsed_template: dict, template_parameters: dict -) -> Optional[str]: +) -> str | None: """only returns string when parsing SAM template, otherwise None""" # TODO: we might also want to override the access key ID to account ID region_before = os.environ.get("AWS_DEFAULT_REGION") diff --git a/localstack-core/localstack/services/cloudformation/engine/types.py b/localstack-core/localstack/services/cloudformation/engine/types.py index 2a4f6efa06031..087c99766b751 100644 --- a/localstack-core/localstack/services/cloudformation/engine/types.py +++ b/localstack-core/localstack/services/cloudformation/engine/types.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional, TypedDict +from collections.abc import Callable +from typing import Any, TypedDict # --------------------- # TYPES @@ -25,16 +26,16 @@ class FuncDetailsValue(TypedDict): # - stack_name # - resources # - resource_id - parameters: Optional[ResourceDefinition | Callable[[dict, str, list[dict], str], dict]] + parameters: ResourceDefinition | Callable[[dict, str, list[dict], str], dict] | None """arguments to the function, or a function that generates the arguments to the function""" # Callable here takes the arguments # - result # - resource_id # - resources # - resource_type - result_handler: Optional[Callable[[dict, str, list[dict], str], None]] + result_handler: Callable[[dict, str, list[dict], str], None] | None """Take the result of the operation and patch the state of the resources, yuck...""" - types: Optional[dict[str, Callable]] + types: dict[str, Callable] | None """Possible type conversions""" diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py index 0f8b334097b4f..3eb0dc1ee67e7 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model.py @@ -2,12 +2,18 @@ import abc import enum +from collections.abc import Generator from itertools import zip_longest -from typing import Any, Final, Generator, Optional, TypedDict, Union, cast +from typing import Any, Final, TypedDict, cast from typing_extensions import TypeVar from localstack.aws.api.cloudformation import ChangeAction +from localstack.services.cloudformation.resource_provider import ResourceProviderExecutor +from localstack.services.cloudformation.v2.types import ( + EngineParameter, + engine_parameter_value, +) from localstack.utils.strings import camel_to_snake_case T = TypeVar("T") @@ -43,7 +49,7 @@ def __contains__(self, item): return False -Maybe = Union[T, NothingType] +Maybe = T | NothingType Nothing = NothingType() @@ -85,7 +91,7 @@ class NormalisedGlobalTransformDefinition(TypedDict): class Scope(str): - _ROOT_SCOPE: Final[str] = str() + _ROOT_SCOPE: Final[str] = "" _SEPARATOR: Final[str] = "/" def __new__(cls, scope: str = _ROOT_SCOPE) -> Scope: @@ -168,8 +174,8 @@ def __init__( node_template: NodeTemplate, ): self.node_template = node_template - self.before_runtime_cache = dict() - self.after_runtime_cache = dict() + self.before_runtime_cache = {} + self.after_runtime_cache = {} class NodeTemplate(ChangeSetNode): @@ -350,6 +356,9 @@ class NodeResource(ChangeSetNode): properties: Final[NodeProperties] condition_reference: Final[Maybe[TerminalValue]] depends_on: Final[Maybe[NodeDependsOn]] + requires_replacement: Final[bool] + deletion_policy: Final[Maybe[ChangeSetTerminal]] + update_replace_policy: Final[Maybe[ChangeSetTerminal]] def __init__( self, @@ -360,6 +369,9 @@ def __init__( properties: NodeProperties, condition_reference: Maybe[TerminalValue], depends_on: Maybe[NodeDependsOn], + requires_replacement: bool, + deletion_policy: Maybe[ChangeSetTerminal], + update_replace_policy: Maybe[ChangeSetTerminal], ): super().__init__(scope=scope, change_type=change_type) self.name = name @@ -367,6 +379,9 @@ def __init__( self.properties = properties self.condition_reference = condition_reference self.depends_on = depends_on + self.requires_replacement = requires_replacement + self.deletion_policy = deletion_policy + self.update_replace_policy = update_replace_policy class NodeProperties(ChangeSetNode): @@ -473,6 +488,8 @@ def __init__(self, scope: Scope, value: Any): ExportKey: Final[str] = "Export" OutputsKey: Final[str] = "Outputs" DependsOnKey: Final[str] = "DependsOn" +DeletionPolicyKey: Final[str] = "DeletionPolicy" +UpdateReplacePolicyKey: Final[str] = "UpdateReplacePolicy" # TODO: expand intrinsic functions set. RefKey: Final[str] = "Ref" RefConditionKey: Final[str] = "Condition" @@ -490,6 +507,7 @@ def __init__(self, scope: Scope, value: Any): FnSplit: Final[str] = "Fn::Split" FnGetAZs: Final[str] = "Fn::GetAZs" FnBase64: Final[str] = "Fn::Base64" +FnImportValue: Final[str] = "Fn::ImportValue" INTRINSIC_FUNCTIONS: Final[set[str]] = { RefKey, RefConditionKey, @@ -507,6 +525,7 @@ def __init__(self, scope: Scope, value: Any): FnSplit, FnGetAZs, FnBase64, + FnImportValue, } @@ -527,16 +546,17 @@ class ChangeSetModel: def __init__( self, - before_template: Optional[dict], - after_template: Optional[dict], - before_parameters: Optional[dict], - after_parameters: Optional[dict], + before_template: dict | None, + after_template: dict | None, + before_parameters: dict | None, + after_parameters: dict[str, EngineParameter] | None, ): self._before_template = before_template or Nothing self._after_template = after_template or Nothing self._before_parameters = before_parameters or Nothing self._after_parameters = after_parameters or Nothing - self._visited_scopes = dict() + self._visited_scopes = {} + # TODO: move this modeling process to the `get_update_model` method as constructors shouldn't do work self._node_template = self._model( before_template=self._before_template, after_template=self._after_template ) @@ -717,13 +737,37 @@ def _resolve_intrinsic_function_fn_if(self, arguments: ChangeSetEntity) -> Chang ) if not isinstance(node_condition, NodeCondition): raise RuntimeError() - change_type = parent_change_type_of([node_condition, *arguments[1:]]) + change_type = parent_change_type_of([node_condition, *arguments.array[1:]]) return change_type + def _resolve_requires_replacement( + self, node_properties: NodeProperties, resource_type: TerminalValue + ) -> bool: + # a bit hacky but we have to load the resource provider executor _and_ resource provider to get the schema + # Note: we don't log the attempt to load the resource provider, we need to make sure this is only done once and we already do this in the executor + resource_provider = ResourceProviderExecutor.try_load_resource_provider(resource_type.value) + if not resource_provider: + # if we don't support a resource, assume an in-place update for simplicity + return False + + create_only_properties: list[str] = resource_provider.SCHEMA.get("createOnlyProperties", []) + # TODO: also hacky: strip the leading `/properties/` string from the definition + # ideally we should use a jsonpath or similar + create_only_properties = [ + property.replace("/properties/", "", 1) for property in create_only_properties + ] + for node_property in node_properties.properties: + if ( + node_property.change_type == ChangeType.MODIFIED + and node_property.name in create_only_properties + ): + return True + return False + def _visit_array( self, scope: Scope, before_array: Maybe[list], after_array: Maybe[list] ) -> NodeArray: - array: list[ChangeSetEntity] = list() + array: list[ChangeSetEntity] = [] for index, (before_value, after_value) in enumerate( zip_longest(before_array, after_array, fillvalue=Nothing) ): @@ -742,7 +786,7 @@ def _visit_object( if isinstance(node_object, NodeObject): return node_object binding_names = self._safe_keys_of(before_object, after_object) - bindings: dict[str, ChangeSetEntity] = dict() + bindings: dict[str, ChangeSetEntity] = {} for binding_name in binding_names: binding_scope, (before_value, after_value) = self._safe_access_in( scope, binding_name, before_object, after_object @@ -844,7 +888,7 @@ def _visit_properties( if isinstance(node_properties, NodeProperties): return node_properties property_names: list[str] = self._safe_keys_of(before_properties, after_properties) - properties: list[NodeProperty] = list() + properties: list[NodeProperty] = [] for property_name in property_names: property_scope, (before_property, after_property) = self._safe_access_in( scope, property_name, before_properties, after_properties @@ -867,6 +911,30 @@ def _visit_type(self, scope: Scope, before_type: Any, after_type: Any) -> Termin raise RuntimeError() return value + def _visit_deletion_policy( + self, scope: Scope, before_deletion_policy: Any, after_deletion_policy: Any + ) -> TerminalValue: + value = self._visit_value( + scope=scope, before_value=before_deletion_policy, after_value=after_deletion_policy + ) + if not isinstance(value, TerminalValue): + # TODO: decide where template schema validation should occur. + raise RuntimeError() + return value + + def _visit_update_replace_policy( + self, scope: Scope, before_update_replace_policy: Any, after_deletion_policy: Any + ) -> TerminalValue: + value = self._visit_value( + scope=scope, + before_value=before_update_replace_policy, + after_value=after_deletion_policy, + ) + if not isinstance(value, TerminalValue): + # TODO: decide where template schema validation should occur. + raise RuntimeError() + return value + def _visit_resource( self, scope: Scope, @@ -912,8 +980,33 @@ def _visit_resource( after_properties=after_properties, ) + deletion_policy = Nothing + scope_deletion_policy, (before_deletion_policy, after_deletion_policy) = ( + self._safe_access_in(scope, DeletionPolicyKey, before_resource, after_resource) + ) + if before_deletion_policy or after_deletion_policy: + deletion_policy = self._visit_deletion_policy( + scope_deletion_policy, before_deletion_policy, after_deletion_policy + ) + + update_replace_policy = Nothing + scope_update_replace_policy, (before_update_replace_policy, after_update_replace_policy) = ( + self._safe_access_in(scope, UpdateReplacePolicyKey, before_resource, after_resource) + ) + if before_update_replace_policy or after_update_replace_policy: + update_replace_policy = self._visit_update_replace_policy( + scope_update_replace_policy, + before_update_replace_policy, + after_update_replace_policy, + ) + change_type = change_type_of( - before_resource, after_resource, [properties, condition_reference, depends_on] + before_resource, + after_resource, + [properties, condition_reference, depends_on, deletion_policy, update_replace_policy], + ) + requires_replacement = self._resolve_requires_replacement( + node_properties=properties, resource_type=terminal_value_type ) node_resource = NodeResource( scope=scope, @@ -923,6 +1016,9 @@ def _visit_resource( properties=properties, condition_reference=condition_reference, depends_on=depends_on, + requires_replacement=requires_replacement, + deletion_policy=deletion_policy, + update_replace_policy=update_replace_policy, ) self._visited_scopes[scope] = node_resource return node_resource @@ -931,7 +1027,7 @@ def _visit_resources( self, scope: Scope, before_resources: Maybe[dict], after_resources: Maybe[dict] ) -> NodeResources: # TODO: investigate type changes behavior. - resources: list[NodeResource] = list() + resources: list[NodeResource] = [] resource_names = self._safe_keys_of(before_resources, after_resources) for resource_name in resource_names: resource_scope, (before_resource, after_resource) = self._safe_access_in( @@ -957,7 +1053,7 @@ def _visit_mapping( def _visit_mappings( self, scope: Scope, before_mappings: Maybe[dict], after_mappings: Maybe[dict] ) -> NodeMappings: - mappings: list[NodeMapping] = list() + mappings: list[NodeMapping] = [] mapping_names = self._safe_keys_of(before_mappings, after_mappings) for mapping_name in mapping_names: scope_mapping, (before_mapping, after_mapping) = self._safe_access_in( @@ -974,9 +1070,22 @@ def _visit_mappings( def _visit_dynamic_parameter(self, parameter_name: str) -> ChangeSetEntity: scope = Scope("Dynamic").open_scope("Parameters") - scope_parameter, (before_parameter, after_parameter) = self._safe_access_in( + scope_parameter, (before_parameter_dct, after_parameter_dct) = self._safe_access_in( scope, parameter_name, self._before_parameters, self._after_parameters ) + + before_parameter = Nothing + if not is_nothing(before_parameter_dct): + before_parameter = before_parameter_dct.get("resolved_value") or engine_parameter_value( + before_parameter_dct + ) + + after_parameter = Nothing + if not is_nothing(after_parameter_dct): + after_parameter = after_parameter_dct.get("resolved_value") or engine_parameter_value( + after_parameter_dct + ) + parameter = self._visit_value( scope=scope_parameter, before_value=before_parameter, after_value=after_parameter ) @@ -1022,7 +1131,7 @@ def _visit_parameters( if isinstance(node_parameters, NodeParameters): return node_parameters parameter_names: list[str] = self._safe_keys_of(before_parameters, after_parameters) - parameters: list[NodeParameter] = list() + parameters: list[NodeParameter] = [] for parameter_name in parameter_names: parameter_scope, (before_parameter, after_parameter) = self._safe_access_in( scope, parameter_name, before_parameters, after_parameters @@ -1092,7 +1201,7 @@ def _visit_conditions( if isinstance(node_conditions, NodeConditions): return node_conditions condition_names: list[str] = self._safe_keys_of(before_conditions, after_conditions) - conditions: list[NodeCondition] = list() + conditions: list[NodeCondition] = [] for condition_name in condition_names: condition_scope, (before_condition, after_condition) = self._safe_access_in( scope, condition_name, before_conditions, after_conditions @@ -1144,7 +1253,7 @@ def _visit_output( def _visit_outputs( self, scope: Scope, before_outputs: Maybe[dict], after_outputs: Maybe[dict] ) -> NodeOutputs: - outputs: list[NodeOutput] = list() + outputs: list[NodeOutput] = [] output_names: list[str] = self._safe_keys_of(before_outputs, after_outputs) for output_name in output_names: scope_output, (before_output, after_output) = self._safe_access_in( @@ -1191,7 +1300,7 @@ def _normalise_transformer_value(value: Maybe[str | list[Any]]) -> Maybe[list[An elif isinstance(value, str): value = [NormalisedGlobalTransformDefinition(Name=value, Parameters=Nothing)] elif isinstance(value, list): - tmp_value = list() + tmp_value = [] for item in value: if isinstance(item, str): tmp_value.append( @@ -1215,7 +1324,7 @@ def _visit_transform( ) -> NodeTransform: before_transform_normalised = self._normalise_transformer_value(before_transform) after_transform_normalised = self._normalise_transformer_value(after_transform) - global_transforms = list() + global_transforms = [] for index, (before_global_transform, after_global_transform) in enumerate( zip_longest(before_transform_normalised, after_transform_normalised, fillvalue=Nothing) ): @@ -1296,8 +1405,8 @@ def _retrieve_condition_if_exists(self, condition_name: str) -> Maybe[NodeCondit conditions_scope, (before_conditions, after_conditions) = self._safe_access_in( Scope(), ConditionsKey, self._before_template, self._after_template ) - before_conditions = before_conditions or dict() - after_conditions = after_conditions or dict() + before_conditions = before_conditions or {} + after_conditions = after_conditions or {} if condition_name in before_conditions or condition_name in after_conditions: condition_scope, (before_condition, after_condition) = self._safe_access_in( conditions_scope, condition_name, before_conditions, after_conditions @@ -1367,7 +1476,7 @@ def _is_intrinsic_function_name(function_name: str) -> bool: @staticmethod def _safe_access_in(scope: Scope, key: str, *objects: Maybe[dict]) -> tuple[Scope, Maybe[Any]]: - results = list() + results = [] for obj in objects: if not isinstance(obj, (dict, NothingType)): raise RuntimeError(f"Invalid definition type at '{obj}'") @@ -1391,7 +1500,7 @@ def _safe_keys_of(*objects: Maybe[dict]) -> list[str]: return keys @staticmethod - def _name_if_intrinsic_function(value: Maybe[Any]) -> Optional[str]: + def _name_if_intrinsic_function(value: Maybe[Any]) -> str | None: if isinstance(value, dict): keys = ChangeSetModel._safe_keys_of(value) if len(keys) == 1: diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py index 14535d44a6f40..19b341d7316a1 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_describer.py @@ -1,9 +1,10 @@ from __future__ import annotations import json -from typing import Final, Optional +from typing import Final import localstack.aws.api.cloudformation as cfn_api +from localstack.aws.api.cloudformation import Replacement from localstack.services.cloudformation.engine.v2.change_set_model import ( NodeIntrinsicFunction, NodeProperty, @@ -34,7 +35,7 @@ def __init__( ): super().__init__(change_set=change_set) self._include_property_values = include_property_values - self._changes = list() + self._changes = [] def get_changes(self) -> cfn_api.Changes: self._changes.clear() @@ -75,7 +76,7 @@ def _resolve_attribute(self, arguments: str | list[str], select_before: bool) -> resource_name=logical_name_of_resource, node_template=self._change_set.update_model.node_template, ) - node_property: Optional[NodeProperty] = self._get_node_property_for( + node_property: NodeProperty | None = self._get_node_property_for( property_name=attribute_name, node_resource=node_resource ) if node_property is not None: @@ -115,9 +116,11 @@ def _register_resource_change( self, logical_id: str, type_: str, - physical_id: Optional[str], - before_properties: Optional[PreprocProperties], - after_properties: Optional[PreprocProperties], + physical_id: str | None, + before_properties: PreprocProperties | None, + after_properties: PreprocProperties | None, + # TODO: remove default + requires_replacement: bool = False, ) -> None: action = cfn_api.ChangeAction.Modify if before_properties is None: @@ -131,20 +134,29 @@ def _register_resource_change( resource_change["ResourceType"] = type_ if physical_id: resource_change["PhysicalResourceId"] = physical_id - if self._include_property_values and before_properties is not None: - before_context_properties = {PropertiesKey: before_properties.properties} - before_context_properties_json_str = json.dumps(before_context_properties) - resource_change["BeforeContext"] = before_context_properties_json_str - if self._include_property_values and after_properties is not None: - after_context_properties = {PropertiesKey: after_properties.properties} - after_context_properties_json_str = json.dumps(after_context_properties) - resource_change["AfterContext"] = after_context_properties_json_str + if self._include_property_values: + if before_properties is not None: + before_context_properties = {PropertiesKey: before_properties.properties} + before_context_properties_json_str = json.dumps(before_context_properties) + resource_change["BeforeContext"] = before_context_properties_json_str + + if after_properties is not None: + after_context_properties = {PropertiesKey: after_properties.properties} + after_context_properties_json_str = json.dumps(after_context_properties) + resource_change["AfterContext"] = after_context_properties_json_str + + if action == cfn_api.ChangeAction.Modify: + # TODO: handle "Conditional" case + resource_change["Replacement"] = ( + Replacement.True_ if requires_replacement else Replacement.False_ + ) + self._changes.append( cfn_api.Change(Type=cfn_api.ChangeType.Resource, ResourceChange=resource_change) ) def _describe_resource_change( - self, name: str, before: Optional[PreprocResource], after: Optional[PreprocResource] + self, name: str, before: PreprocResource | None, after: PreprocResource | None ) -> None: if before == after: # unchanged: nothing to do. @@ -159,6 +171,7 @@ def _describe_resource_change( type_=before.resource_type, before_properties=before.properties, after_properties=after.properties, + requires_replacement=after.requires_replacement, ) # Case: type migration. # TODO: Add test to assert that on type change the resources are replaced. @@ -213,3 +226,20 @@ def visit_node_resource( if not is_nothing(after_resource) and after_resource.physical_resource_id is None: after_resource.physical_resource_id = CHANGESET_KNOWN_AFTER_APPLY return delta + + def visit_node_intrinsic_function_fn_import_value( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + delta = super().visit_node_intrinsic_function_fn_import_value( + node_intrinsic_function=node_intrinsic_function + ) + after_value = delta.after + if is_nothing(after_value) and self._include_property_values: + # TODO find correct way to obtain parent resource + resource_name = node_intrinsic_function.scope.split("/")[2] + export_name = node_intrinsic_function.arguments.value + + self._change_set.status_reason = f"[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [{resource_name}] because: No export named {export_name}" + delta.after = "{{changeSet:KNOWN_AFTER_APPLY}}" + + return delta diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py index 77105d6273d01..39f6a46dd2dd2 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py @@ -2,24 +2,27 @@ import logging import uuid from dataclasses import dataclass -from datetime import datetime, timezone -from typing import Final, Optional +from datetime import UTC, datetime +from typing import Final, Protocol from localstack import config from localstack.aws.api.cloudformation import ( ChangeAction, + Output, ResourceStatus, StackStatus, ) from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY from localstack.services.cloudformation.analytics import track_resource_operation from localstack.services.cloudformation.deployment_utils import log_not_available_message -from localstack.services.cloudformation.engine.parameters import resolve_ssm_parameter +from localstack.services.cloudformation.engine.template_deployer import REGEX_OUTPUT_APIGATEWAY from localstack.services.cloudformation.engine.v2.change_set_model import ( NodeDependsOn, NodeOutput, - NodeParameter, NodeResource, + TerminalValueCreated, + TerminalValueModified, + TerminalValueUnchanged, is_nothing, ) from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( @@ -38,6 +41,7 @@ ResourceProviderPayload, ) from localstack.services.cloudformation.v2.entities import ChangeSet, ResolvedResource +from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) @@ -47,51 +51,49 @@ @dataclass class ChangeSetModelExecutorResult: resources: dict[str, ResolvedResource] - parameters: dict - outputs: dict + outputs: list[Output] + + +class DeferredAction(Protocol): + def __call__(self) -> None: ... class ChangeSetModelExecutor(ChangeSetModelPreproc): # TODO: add typing for resolved resources and parameters. resources: Final[dict[str, ResolvedResource]] - outputs: Final[dict] - resolved_parameters: Final[dict] + outputs: Final[list[Output]] + _deferred_actions: list[DeferredAction] def __init__(self, change_set: ChangeSet): super().__init__(change_set=change_set) - self.resources = dict() - self.outputs = dict() - self.resolved_parameters = dict() + self.resources = {} + self.outputs = [] + self._deferred_actions = [] + self.resource_provider_executor = ResourceProviderExecutor( + stack_name=change_set.stack.stack_name, + stack_id=change_set.stack.stack_id, + ) - # TODO: use a structured type for the return value def execute(self) -> ChangeSetModelExecutorResult: + # constructive process self.process() + + if self._deferred_actions: + self._change_set.stack.set_stack_status(StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS) + + # perform all deferred actions such as deletions. These must happen in reverse from their + # defined order so that resource dependencies are honoured + # TODO: errors will stop all rollbacks; get parity on this behaviour + for action in self._deferred_actions[::-1]: + action() + return ChangeSetModelExecutorResult( - resources=self.resources, parameters=self.resolved_parameters, outputs=self.outputs + resources=self.resources, + outputs=self.outputs, ) - def visit_node_parameter(self, node_parameter: NodeParameter) -> PreprocEntityDelta: - delta = super().visit_node_parameter(node_parameter) - - # handle dynamic references, e.g. references to SSM parameters - # TODO: support more parameter types - parameter_type: str = node_parameter.type_.value - if parameter_type.startswith("AWS::SSM"): - if parameter_type in [ - "AWS::SSM::Parameter::Value", - "AWS::SSM::Parameter::Value", - "AWS::SSM::Parameter::Value", - ]: - delta.after = resolve_ssm_parameter( - account_id=self._change_set.account_id, - region_name=self._change_set.region_name, - stack_parameter_value=delta.after, - ) - else: - raise Exception(f"Unsupported stack parameter type: {parameter_type}") - - self.resolved_parameters[node_parameter.name] = delta.after - return delta + def _defer_action(self, action: DeferredAction): + self._deferred_actions.append(action) def _get_physical_id(self, logical_resource_id, strict: bool = True) -> str | None: physical_resource_id = None @@ -113,12 +115,13 @@ def _get_physical_id(self, logical_resource_id, strict: bool = True) -> str | No def _process_event( self, + *, action: ChangeAction, logical_resource_id, event_status: OperationStatus, + resource_type: str, special_action: str = None, reason: str = None, - resource_type=None, ): status_from_action = special_action or EventOperationFromAction[action.value] if event_status == OperationStatus.SUCCESS: @@ -128,7 +131,7 @@ def _process_event( self._change_set.stack.set_resource_status( logical_resource_id=logical_resource_id, - physical_resource_id=self._get_physical_id(logical_resource_id, False), + physical_resource_id=self._get_physical_id(logical_resource_id, False) or "", resource_type=resource_type, status=ResourceStatus(status), resource_status_reason=reason, @@ -182,11 +185,11 @@ def visit_node_resource( delta = super().visit_node_resource(node_resource=node_resource) except Exception as e: self._process_event( - node_resource.change_type.to_change_action(), - node_resource.name, - OperationStatus.FAILED, - reason=str(e), + action=node_resource.change_type.to_change_action(), + logical_resource_id=node_resource.name, + event_status=OperationStatus.FAILED, resource_type=node_resource.type_.value, + reason=str(e), ) raise e @@ -202,7 +205,7 @@ def visit_node_resource( # references or other downstream operations. if not is_nothing(before): before_logical_id = delta.before.logical_id - before_resource = self._before_resolved_resources.get(before_logical_id, dict()) + before_resource = self._before_resolved_resources.get(before_logical_id, {}) self.resources[before_logical_id] = before_resource # Update the latest version of this resource for downstream references. @@ -221,11 +224,20 @@ def visit_node_output( after = delta.after if is_nothing(after) or (isinstance(after, PreprocOutput) and after.condition is False): return delta - self.outputs[delta.after.name] = delta.after.value + + output = Output( + OutputKey=delta.after.name, + OutputValue=delta.after.value, + # TODO + # Description=delta.after.description + ) + if after.export: + output["ExportName"] = after.export["Name"] + self.outputs.append(output) return delta def _execute_resource_change( - self, name: str, before: Optional[PreprocResource], after: Optional[PreprocResource] + self, name: str, before: PreprocResource | None, after: PreprocResource | None ) -> None: # Changes are to be made about this resource. # TODO: this logic is a POC and should be revised. @@ -236,21 +248,66 @@ def _execute_resource_change( # XXX hacky, stick the previous resources' properties into the payload before_properties = self._merge_before_properties(name, before) - self._process_event(ChangeAction.Modify, name, OperationStatus.IN_PROGRESS) - event = self._execute_resource_action( + self._process_event( action=ChangeAction.Modify, logical_resource_id=name, - resource_type=before.resource_type, - before_properties=before_properties, - after_properties=after.properties, - ) - self._process_event( - ChangeAction.Modify, - name, - event.status, - reason=event.message, + event_status=OperationStatus.IN_PROGRESS, resource_type=before.resource_type, ) + if after.requires_replacement: + event = self._execute_resource_action( + action=ChangeAction.Add, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=None, + after_properties=after.properties, + ) + self._process_event( + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + + def cleanup(): + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=OperationStatus.IN_PROGRESS, + resource_type=before.resource_type, + ) + event = self._execute_resource_action( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=None, + ) + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + + self._defer_action(cleanup) + else: + event = self._execute_resource_action( + action=ChangeAction.Modify, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=after.properties, + ) + self._process_event( + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) # Case: type migration. # TODO: Add test to assert that on type change the resources are replaced. else: @@ -258,21 +315,24 @@ def _execute_resource_change( before_properties = self._merge_before_properties(name, before) # Register a Removed for the previous type. - event = self._execute_resource_action( - action=ChangeAction.Remove, - logical_resource_id=name, - resource_type=before.resource_type, - before_properties=before_properties, - after_properties=None, - ) - # Register a Create for the next type. - self._process_event( - ChangeAction.Modify, - name, - event.status, - reason=event.message, - resource_type=before.resource_type, - ) + def perform_deletion(): + event = self._execute_resource_action( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=None, + ) + self._process_event( + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + + self._defer_action(perform_deletion) + event = self._execute_resource_action( action=ChangeAction.Add, logical_resource_id=name, @@ -281,43 +341,47 @@ def _execute_resource_change( after_properties=after.properties, ) self._process_event( - ChangeAction.Modify, - name, - event.status, - reason=event.message, + action=ChangeAction.Modify, + logical_resource_id=name, + event_status=event.status, resource_type=before.resource_type, + reason=event.message, ) elif not is_nothing(before): # Case: removal # XXX hacky, stick the previous resources' properties into the payload # XXX hacky, stick the previous resources' properties into the payload before_properties = self._merge_before_properties(name, before) - self._process_event( - ChangeAction.Remove, - name, - OperationStatus.IN_PROGRESS, - resource_type=before.resource_type, - ) - event = self._execute_resource_action( - action=ChangeAction.Remove, - logical_resource_id=name, - resource_type=before.resource_type, - before_properties=before_properties, - after_properties=None, - ) - self._process_event( - ChangeAction.Remove, - name, - event.status, - reason=event.message, - resource_type=before.resource_type, - ) + + def perform_deletion(): + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + event_status=OperationStatus.IN_PROGRESS, + ) + event = self._execute_resource_action( + action=ChangeAction.Remove, + logical_resource_id=name, + resource_type=before.resource_type, + before_properties=before_properties, + after_properties=None, + ) + self._process_event( + action=ChangeAction.Remove, + logical_resource_id=name, + event_status=event.status, + resource_type=before.resource_type, + reason=event.message, + ) + + self._defer_action(perform_deletion) elif not is_nothing(after): # Case: addition self._process_event( - ChangeAction.Add, - name, - OperationStatus.IN_PROGRESS, + action=ChangeAction.Add, + logical_resource_id=name, + event_status=OperationStatus.IN_PROGRESS, resource_type=after.resource_type, ) event = self._execute_resource_action( @@ -328,11 +392,11 @@ def _execute_resource_change( after_properties=after.properties, ) self._process_event( - ChangeAction.Add, - name, - event.status, - reason=event.message, + action=ChangeAction.Add, + logical_resource_id=name, + event_status=event.status, resource_type=after.resource_type, + reason=event.message, ) def _merge_before_properties( @@ -351,13 +415,10 @@ def _execute_resource_action( action: ChangeAction, logical_resource_id: str, resource_type: str, - before_properties: Optional[PreprocProperties], - after_properties: Optional[PreprocProperties], + before_properties: PreprocProperties | None, + after_properties: PreprocProperties | None, ) -> ProgressEvent: LOG.debug("Executing resource action: %s for resource '%s'", action, logical_resource_id) - resource_provider_executor = ResourceProviderExecutor( - stack_name=self._change_set.stack.stack_name, stack_id=self._change_set.stack.stack_id - ) payload = self.create_resource_provider_payload( action=action, logical_resource_id=logical_resource_id, @@ -365,13 +426,15 @@ def _execute_resource_action( before_properties=before_properties, after_properties=after_properties, ) - resource_provider = resource_provider_executor.try_load_resource_provider(resource_type) + resource_provider = self.resource_provider_executor.try_load_resource_provider( + resource_type + ) track_resource_operation(action, resource_type, missing=resource_provider is not None) extra_resource_properties = {} if resource_provider is not None: try: - event = resource_provider_executor.deploy_loop( + event = self.resource_provider_executor.deploy_loop( resource_provider, extra_resource_properties, payload ) except Exception as e: @@ -379,7 +442,7 @@ def _execute_resource_action( LOG.warning( "Resource provider operation failed: '%s'", reason, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) event = ProgressEvent( OperationStatus.FAILED, @@ -422,24 +485,26 @@ def _execute_resource_action( # TODO: avoid the use of setdefault (debuggability/readability) # TODO: review the use of merge - status_from_action = EventOperationFromAction[action.value] - physical_resource_id = ( - extra_resource_properties["PhysicalResourceId"] - if resource_provider - else MOCKED_REFERENCE - ) - resolved_resource = ResolvedResource( - Properties=event.resource_model, - LogicalResourceId=logical_resource_id, - Type=resource_type, - LastUpdatedTimestamp=datetime.now(timezone.utc), - ResourceStatus=ResourceStatus(f"{status_from_action}_COMPLETE"), - PhysicalResourceId=physical_resource_id, - ) - # TODO: do we actually need this line? - resolved_resource.update(extra_resource_properties) - - self.resources[logical_resource_id] = resolved_resource + # Don't update the resolved resources if we have deleted that resource + if action != ChangeAction.Remove: + status_from_action = EventOperationFromAction[action.value] + physical_resource_id = ( + extra_resource_properties["PhysicalResourceId"] + if resource_provider + else MOCKED_REFERENCE + ) + resolved_resource = ResolvedResource( + Properties=event.resource_model, + LogicalResourceId=logical_resource_id, + Type=resource_type, + LastUpdatedTimestamp=datetime.now(UTC), + ResourceStatus=ResourceStatus(f"{status_from_action}_COMPLETE"), + PhysicalResourceId=physical_resource_id, + ) + # TODO: do we actually need this line? + resolved_resource.update(extra_resource_properties) + + self.resources[logical_resource_id] = resolved_resource case OperationStatus.FAILED: reason = event.message @@ -456,9 +521,9 @@ def create_resource_provider_payload( action: ChangeAction, logical_resource_id: str, resource_type: str, - before_properties: Optional[PreprocProperties], - after_properties: Optional[PreprocProperties], - ) -> Optional[ResourceProviderPayload]: + before_properties: PreprocProperties | None, + after_properties: PreprocProperties | None, + ) -> ResourceProviderPayload | None: # FIXME: use proper credentials creds: Credentials = { "accessKeyId": self._change_set.stack.account_id, @@ -506,3 +571,44 @@ def create_resource_provider_payload( }, } return resource_provider_payload + + @staticmethod + def _replace_url_outputs_if_required(value: str) -> str: + api_match = REGEX_OUTPUT_APIGATEWAY.match(value) + if api_match and value not in config.CFN_STRING_REPLACEMENT_DENY_LIST: + prefix = api_match[1] + host = api_match[2] + path = api_match[3] + port = localstack_host().port + value = f"{prefix}{host}:{port}/{path}" + return value + + return value + + def visit_terminal_value_created( + self, value: TerminalValueCreated + ) -> PreprocEntityDelta[str, str]: + if isinstance(value.value, str): + after = self._replace_url_outputs_if_required(value.value) + else: + after = value.value + return PreprocEntityDelta(after=after) + + def visit_terminal_value_modified( + self, value: TerminalValueModified + ) -> PreprocEntityDelta[str, str]: + # we only need to transform the after + if isinstance(value.modified_value, str): + after = self._replace_url_outputs_if_required(value.modified_value) + else: + after = value.modified_value + return PreprocEntityDelta(before=value.value, after=after) + + def visit_terminal_value_unchanged( + self, terminal_value_unchanged: TerminalValueUnchanged + ) -> PreprocEntityDelta: + if isinstance(terminal_value_unchanged.value, str): + value = self._replace_url_outputs_if_required(terminal_value_unchanged.value) + else: + value = terminal_value_unchanged.value + return PreprocEntityDelta(before=value, after=value) diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py index eb91eaee3be98..e49cdc32be98e 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_preproc.py @@ -3,18 +3,14 @@ import base64 import copy import re -from typing import Any, Callable, Final, Generic, Optional, TypeVar +from collections.abc import Callable +from typing import Any, Final, Generic, TypeVar from botocore.exceptions import ClientError from localstack import config from localstack.aws.api.ec2 import AvailabilityZoneList, DescribeAvailabilityZonesResult from localstack.aws.connect import connect_to -from localstack.services.cloudformation.engine.transformers import ( - Transformer, - execute_macro, - transformers, -) from localstack.services.cloudformation.engine.v2.change_set_model import ( ChangeSetEntity, ChangeType, @@ -47,7 +43,14 @@ from localstack.services.cloudformation.engine.v2.change_set_model_visitor import ( ChangeSetModelVisitor, ) -from localstack.services.cloudformation.stores import get_cloudformation_store +from localstack.services.cloudformation.engine.v2.resolving import ( + extract_dynamic_reference, + perform_dynamic_reference_lookup, +) +from localstack.services.cloudformation.engine.validations import ValidationError +from localstack.services.cloudformation.stores import ( + exports_map, +) from localstack.services.cloudformation.v2.entities import ChangeSet from localstack.utils.aws.arns import get_partition from localstack.utils.objects import get_value_from_path @@ -73,6 +76,8 @@ MOCKED_REFERENCE = "unknown" +VALID_LOGICAL_RESOURCE_ID_RE = re.compile(r"^[A-Za-z0-9]+$") + class PreprocEntityDelta(Generic[TBefore, TAfter]): before: Maybe[TBefore] @@ -102,20 +107,22 @@ def __eq__(self, other): class PreprocResource: logical_id: str - physical_resource_id: Optional[str] - condition: Optional[bool] + physical_resource_id: str | None + condition: bool | None resource_type: str properties: PreprocProperties - depends_on: Optional[list[str]] + depends_on: list[str] | None + requires_replacement: bool def __init__( self, logical_id: str, physical_resource_id: str, - condition: Optional[bool], + condition: bool | None, resource_type: str, properties: PreprocProperties, - depends_on: Optional[list[str]], + depends_on: list[str] | None, + requires_replacement: bool, ): self.logical_id = logical_id self.physical_resource_id = physical_resource_id @@ -123,6 +130,7 @@ def __init__( self.resource_type = resource_type self.properties = properties self.depends_on = depends_on + self.requires_replacement = requires_replacement @staticmethod def _compare_conditions(c1: bool, c2: bool): @@ -147,10 +155,10 @@ def __eq__(self, other): class PreprocOutput: name: str value: Any - export: Optional[Any] - condition: Optional[bool] + export: Any | None + condition: bool | None - def __init__(self, name: str, value: Any, export: Optional[Any], condition: Optional[bool]): + def __init__(self, name: str, value: Any, export: Any | None, condition: bool | None): self.name = name self.value = value self.export = export @@ -178,8 +186,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor): def __init__(self, change_set: ChangeSet): self._change_set = change_set self._before_resolved_resources = change_set.stack.resolved_resources - self._before_cache = dict() - self._after_cache = dict() + self._before_cache = {} + self._after_cache = {} def _setup_runtime_cache(self) -> None: runtime_cache_key = self.__class__.__name__ @@ -218,11 +226,13 @@ def _get_node_resource_for( if node_resource.name == resource_name: self.visit(node_resource) return node_resource - raise RuntimeError(f"No resource '{resource_name}' was found") + raise ValidationError( + f"Template format error: Unresolved resource dependencies [{resource_name}] in the Resources block of the template" + ) def _get_node_property_for( self, property_name: str, node_resource: NodeResource - ) -> Optional[NodeProperty]: + ) -> NodeProperty | None: # TODO: this could be improved with hashmap lookups if the Node contained bindings and not lists. for node_property in node_resource.properties.properties: if node_property.name == property_name: @@ -233,7 +243,8 @@ def _get_node_property_for( def _deployed_property_value_of( self, resource_logical_id: str, property_name: str, resolved_resources: dict ) -> Any: - # TODO: typing around resolved resources is needed and should be reflected here. + # We have to override this function to make sure it does not try to access the + # resolved resource # Before we can obtain deployed value for a resource, we need to first ensure to # process the resource if this wasn't processed already. Ideally, values should only @@ -248,9 +259,9 @@ def _deployed_property_value_of( raise RuntimeError( f"No deployed instances of resource '{resource_logical_id}' were found" ) - properties = resolved_resource.get("Properties", dict()) + properties = resolved_resource.get("Properties", {}) # support structured properties, e.g. NestedStack.Outputs.OutputName - property_value: Optional[Any] = get_value_from_path(properties, property_name) + property_value: Any | None = get_value_from_path(properties, property_name) if property_value: if not isinstance(property_value, str): @@ -277,7 +288,7 @@ def _before_deployed_property_value_of( def _after_deployed_property_value_of( self, resource_logical_id: str, property_name: str - ) -> Optional[str]: + ) -> str | None: return self._before_deployed_property_value_of( resource_logical_id=resource_logical_id, property_name=property_name ) @@ -367,8 +378,12 @@ def _resolve_mapping( node_mapping: NodeMapping = self._get_node_mapping(map_name=map_name) top_level_value = node_mapping.bindings.bindings.get(top_level_key) if not isinstance(top_level_value, NodeObject): - raise RuntimeError() + error_key = "::".join([map_name, top_level_key, second_level_key]) + raise ValidationError(f"Template error: Unable to get mapping for {error_key}") second_level_value = top_level_value.bindings.get(second_level_key) + if not isinstance(second_level_value, (TerminalValue, NodeArray, NodeObject)): + error_key = "::".join([map_name, top_level_key, second_level_key]) + raise ValidationError(f"Template error: Unable to get mapping for {error_key}") mapping_value_delta = self.visit(second_level_value) return mapping_value_delta @@ -465,8 +480,8 @@ def visit_node_divergence(self, node_divergence: NodeDivergence) -> PreprocEntit def visit_node_object(self, node_object: NodeObject) -> PreprocEntityDelta: node_change_type = node_object.change_type - before = dict() if node_change_type != ChangeType.CREATED else Nothing - after = dict() if node_change_type != ChangeType.REMOVED else Nothing + before = {} if node_change_type != ChangeType.CREATED else Nothing + after = {} if node_change_type != ChangeType.REMOVED else Nothing for name, change_set_entity in node_object.bindings.items(): delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity) delta_before = delta.before @@ -491,7 +506,7 @@ def _resolve_attribute(self, arguments: str | list[str], select_before: bool) -> resource_name=logical_name_of_resource, node_template=self._change_set.update_model.node_template, ) - node_property: Optional[NodeProperty] = self._get_node_property_for( + node_property: NodeProperty | None = self._get_node_property_for( property_name=attribute_name, node_resource=node_resource ) if node_property is not None: @@ -549,21 +564,38 @@ def _compute_fn_equals(args: list[Any]) -> bool: def visit_node_intrinsic_function_fn_if( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: - def _compute_delta_for_if_statement(args: list[Any]) -> PreprocEntityDelta: - condition_name = args[0] - boolean_expression_delta = self._resolve_condition(logical_id=condition_name) - return PreprocEntityDelta( - before=args[1] if boolean_expression_delta.before else args[2], - after=args[1] if boolean_expression_delta.after else args[2], + # `if` needs to be short-circuiting i.e. if the condition is True we don't evaluate the + # False branch. If the condition is False, we don't evaluate the True branch. + if len(node_intrinsic_function.arguments.array) != 3: + raise ValueError( + f"Incorrectly constructed Fn::If usage, expected 3 arguments, found {len(node_intrinsic_function.arguments.array)}" ) - arguments_delta = self.visit(node_intrinsic_function.arguments) - delta = self._cached_apply( - scope=node_intrinsic_function.scope, - arguments_delta=arguments_delta, - resolver=_compute_delta_for_if_statement, - ) - return delta + condition_delta = self.visit(node_intrinsic_function.arguments.array[0]) + if_delta = PreprocEntityDelta() + if not is_nothing(condition_delta.before): + node_condition = self._get_node_condition_if_exists( + condition_name=condition_delta.before + ) + condition_value = self.visit(node_condition).before + if condition_value: + arg_delta = self.visit(node_intrinsic_function.arguments.array[1]) + else: + arg_delta = self.visit(node_intrinsic_function.arguments.array[2]) + if_delta.before = arg_delta.before + + if not is_nothing(condition_delta.after): + node_condition = self._get_node_condition_if_exists( + condition_name=condition_delta.after + ) + condition_value = self.visit(node_condition).after + if condition_value: + arg_delta = self.visit(node_intrinsic_function.arguments.array[1]) + else: + arg_delta = self.visit(node_intrinsic_function.arguments.array[2]) + if_delta.after = arg_delta.after + + return if_delta def visit_node_intrinsic_function_fn_and( self, node_intrinsic_function: NodeIntrinsicFunction @@ -609,65 +641,6 @@ def _compute_fn_not(arg: bool) -> bool: ) return delta - def _compute_fn_transform(self, args: dict[str, Any]) -> Any: - # TODO: add typing to arguments before this level. - # TODO: add schema validation - # TODO: add support for other transform types - - account_id = self._change_set.account_id - region_name = self._change_set.region_name - transform_name: str = args.get("Name") - if not isinstance(transform_name, str): - raise RuntimeError("Invalid or missing Fn::Transform 'Name' argument") - transform_parameters: dict = args.get("Parameters") - if not isinstance(transform_parameters, dict): - raise RuntimeError("Invalid or missing Fn::Transform 'Parameters' argument") - - if transform_name in transformers: - # TODO: port and refactor this 'transformers' logic to this package. - builtin_transformer_class = transformers[transform_name] - builtin_transformer: Transformer = builtin_transformer_class() - transform_output: Any = builtin_transformer.transform( - account_id=account_id, region_name=region_name, parameters=transform_parameters - ) - return transform_output - - macros_store = get_cloudformation_store( - account_id=account_id, region_name=region_name - ).macros - if transform_name in macros_store: - # TODO: this formatting of stack parameters is odd but required to integrate with v1 execute_macro util. - # consider porting this utils and passing the plain list of parameters instead. - stack_parameters = { - parameter["ParameterKey"]: parameter - for parameter in self._change_set.stack.parameters - } - transform_output: Any = execute_macro( - account_id=account_id, - region_name=region_name, - parsed_template=dict(), # TODO: review the requirements for this argument. - macro=args, # TODO: review support for non dict bindings (v1). - stack_parameters=stack_parameters, - transformation_parameters=transform_parameters, - is_intrinsic=True, - ) - return transform_output - - raise RuntimeError( - f"Unsupported transform function '{transform_name}' in '{self._change_set.stack.stack_name}'" - ) - - def visit_node_intrinsic_function_fn_transform( - self, node_intrinsic_function: NodeIntrinsicFunction - ) -> PreprocEntityDelta: - arguments_delta = self.visit(node_intrinsic_function.arguments) - delta = self._cached_apply( - scope=node_intrinsic_function.scope, - arguments_delta=arguments_delta, - resolver=self._compute_fn_transform, - ) - return delta - def visit_node_intrinsic_function_fn_sub( self, node_intrinsic_function: NodeIntrinsicFunction ) -> PreprocEntityDelta: @@ -677,7 +650,7 @@ def _compute_sub(args: str | list[Any], select_before: bool) -> str: sub_parameters: dict if isinstance(args, str): string_template = args - sub_parameters = dict() + sub_parameters = {} elif ( isinstance(args, list) and len(args) == 2 @@ -784,7 +757,7 @@ def _compute_fn_join(args: list[Any]) -> str | NothingType: if values == "": return "" raise RuntimeError(f"Invalid arguments list definition for Fn::Join: '{args}'") - str_values: list[str] = list() + str_values: list[str] = [] for value in values: if value is None: continue @@ -925,8 +898,8 @@ def visit_node_mapping(self, node_mapping: NodeMapping) -> PreprocEntityDelta: def visit_node_parameters( self, node_parameters: NodeParameters ) -> PreprocEntityDelta[dict[str, Any], dict[str, Any]]: - before_parameters = dict() - after_parameters = dict() + before_parameters = {} + after_parameters = {} for parameter in node_parameters.parameters: parameter_delta = self.visit(parameter) parameter_before = parameter_delta.before @@ -972,8 +945,8 @@ def _resource_physical_resource_id_from( self, logical_resource_id: str, resolved_resources: dict ) -> str: # TODO: typing around resolved resources is needed and should be reflected here. - resolved_resource = resolved_resources.get(logical_resource_id, dict()) - physical_resource_id: Optional[str] = resolved_resource.get("PhysicalResourceId") + resolved_resource = resolved_resources.get(logical_resource_id, {}) + physical_resource_id: str | None = resolved_resource.get("PhysicalResourceId") if not isinstance(physical_resource_id, str): raise RuntimeError(f"No PhysicalResourceId found for resource '{logical_resource_id}'") return physical_resource_id @@ -1028,8 +1001,8 @@ def _delta_of_condition(name: str) -> PreprocEntityDelta: def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta: node_change_type = node_array.change_type - before = list() if node_change_type != ChangeType.CREATED else Nothing - after = list() if node_change_type != ChangeType.REMOVED else Nothing + before = [] if node_change_type != ChangeType.CREATED else Nothing + after = [] if node_change_type != ChangeType.REMOVED else Nothing for change_set_entity in node_array.array: delta: PreprocEntityDelta = self.visit(change_set_entity=change_set_entity) delta_before = delta.before @@ -1041,14 +1014,30 @@ def visit_node_array(self, node_array: NodeArray) -> PreprocEntityDelta: return PreprocEntityDelta(before=before, after=after) def visit_node_property(self, node_property: NodeProperty) -> PreprocEntityDelta: - return self.visit(node_property.value) + # TODO: what about other positions? + value = self.visit(node_property.value) + if not is_nothing(value.before): + if dynamic_ref := extract_dynamic_reference(value.before): + value.before = perform_dynamic_reference_lookup( + reference=dynamic_ref, + account_id=self._change_set.account_id, + region_name=self._change_set.region_name, + ) + if not is_nothing(value.after): + if dynamic_ref := extract_dynamic_reference(value.after): + value.after = perform_dynamic_reference_lookup( + reference=dynamic_ref, + account_id=self._change_set.account_id, + region_name=self._change_set.region_name, + ) + return value def visit_node_properties( self, node_properties: NodeProperties ) -> PreprocEntityDelta[PreprocProperties, PreprocProperties]: node_change_type = node_properties.change_type - before_bindings = dict() if node_change_type != ChangeType.CREATED else Nothing - after_bindings = dict() if node_change_type != ChangeType.REMOVED else Nothing + before_bindings = {} if node_change_type != ChangeType.CREATED else Nothing + after_bindings = {} if node_change_type != ChangeType.REMOVED else Nothing for node_property in node_properties.properties: property_name = node_property.name delta = self.visit(node_property) @@ -1091,6 +1080,10 @@ def _resolve_resource_condition_reference(self, reference: TerminalValue) -> Pre def visit_node_resource( self, node_resource: NodeResource ) -> PreprocEntityDelta[PreprocResource, PreprocResource]: + if not VALID_LOGICAL_RESOURCE_ID_RE.match(node_resource.name): + raise ValidationError( + f"Template format error: Resource name {node_resource.name} is non alphanumeric." + ) change_type = node_resource.change_type condition_before = Nothing condition_after = Nothing @@ -1127,6 +1120,7 @@ def visit_node_resource( resource_type=type_delta.before, properties=properties_delta.before, depends_on=depends_on_before, + requires_replacement=False, ) if change_type != ChangeType.REMOVED and is_nothing(condition_after) or condition_after: logical_resource_id = node_resource.name @@ -1143,6 +1137,7 @@ def visit_node_resource( resource_type=type_delta.after, properties=properties_delta.after, depends_on=depends_on_after, + requires_replacement=node_resource.requires_replacement, ) return PreprocEntityDelta(before=before, after=after) @@ -1189,8 +1184,8 @@ def visit_node_output( def visit_node_outputs( self, node_outputs: NodeOutputs ) -> PreprocEntityDelta[list[PreprocOutput], list[PreprocOutput]]: - before: list[PreprocOutput] = list() - after: list[PreprocOutput] = list() + before: list[PreprocOutput] = [] + after: list[PreprocOutput] = [] for node_output in node_outputs.outputs: output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output) output_before = output_delta.before @@ -1200,3 +1195,24 @@ def visit_node_outputs( if not is_nothing(output_after): after.append(output_after) return PreprocEntityDelta(before=before, after=after) + + def visit_node_intrinsic_function_fn_import_value( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + def _compute_fn_import_value(string) -> str: + if not isinstance(string, str): + raise RuntimeError(f"Invalid parameter for import: '{string}'") + + exports = exports_map( + account_id=self._change_set.account_id, region_name=self._change_set.region_name + ) + + return exports.get(string, {}).get("Value") or Nothing + + arguments_delta = self.visit(node_intrinsic_function.arguments) + delta = self._cached_apply( + scope=node_intrinsic_function.scope, + arguments_delta=arguments_delta, + resolver=_compute_fn_import_value, + ) + return delta diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py index 70981d014747c..04499c0724a56 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_transform.py @@ -1,12 +1,15 @@ import copy import logging import os -from typing import Any, Final, Optional, TypedDict +from typing import Any, Final, TypedDict import boto3 +from botocore.exceptions import ClientError from samtranslator.translator.transform import transform as transform_sam +from localstack.aws.connect import connect_to from localstack.services.cloudformation.engine.policy_loader import create_policy_loader +from localstack.services.cloudformation.engine.template_preparer import parse_template from localstack.services.cloudformation.engine.transformers import ( FailedTransformationException, execute_macro, @@ -27,12 +30,14 @@ ) from localstack.services.cloudformation.stores import get_cloudformation_store from localstack.services.cloudformation.v2.entities import ChangeSet +from localstack.utils import testutil LOG = logging.getLogger(__name__) SERVERLESS_TRANSFORM = "AWS::Serverless-2016-10-31" EXTENSIONS_TRANSFORM = "AWS::LanguageExtensions" SECRETSMANAGER_TRANSFORM = "AWS::SecretsManager-2020-07-23" +INCLUDE_TRANSFORM = "AWS::Include" _SCOPE_TRANSFORM_TEMPLATE_OUTCOME: Final[Scope] = Scope("TRANSFORM_TEMPLATE_OUTCOME") @@ -51,7 +56,7 @@ class TransformPreprocParameter(TypedDict): # TODO: expand ParameterKey: str ParameterValue: Any - ParameterType: Optional[str] + ParameterType: str | None class ChangeSetModelTransform(ChangeSetModelPreproc): @@ -65,8 +70,8 @@ def __init__( change_set: ChangeSet, before_parameters: dict, after_parameters: dict, - before_template: Optional[dict], - after_template: Optional[dict], + before_template: dict | None, + after_template: dict | None, ): super().__init__(change_set=change_set) self._before_parameters = before_parameters @@ -138,6 +143,32 @@ def _apply_global_serverless_transformation( if region_before is not None: os.environ["AWS_DEFAULT_REGION"] = region_before + @staticmethod + def _apply_global_include( + global_transform: GlobalTransform, template: dict, parameters: dict, account_id, region_name + ) -> dict: + location = global_transform.parameters.get("Location") + if not location or not location.startswith("s3://"): + raise FailedTransformationException( + transformation=INCLUDE_TRANSFORM, + message="Unexpected Location parameter for AWS::Include transformer: %s" % location, + ) + + s3_client = connect_to(aws_access_key_id=account_id, region_name=region_name).s3 + bucket, _, path = location.removeprefix("s3://").partition("/") + try: + content = testutil.download_s3_object(s3_client, bucket, path) + except ClientError: + raise FailedTransformationException( + transformation=INCLUDE_TRANSFORM, + message="Error downloading S3 object '%s/%s'" % (bucket, path), + ) + try: + template_to_include = parse_template(content) + except Exception as e: + raise FailedTransformationException(transformation=INCLUDE_TRANSFORM, message=str(e)) + return {**template, **template_to_include} + @staticmethod def _apply_global_macro_transformation( account_id: str, @@ -145,7 +176,7 @@ def _apply_global_macro_transformation( global_transform: GlobalTransform, template: dict, parameters: dict, - ) -> Optional[dict]: + ) -> dict | None: macro_name = global_transform.name macros_store = get_cloudformation_store( account_id=account_id, region_name=region_name @@ -153,7 +184,7 @@ def _apply_global_macro_transformation( macro = macros_store.get(macro_name) if macro is None: raise RuntimeError(f"No definitions for global transform '{macro_name}'") - transformation_parameters = global_transform.parameters or dict() + transformation_parameters = global_transform.parameters or {} transformed_template = execute_macro( account_id, region_name, @@ -182,6 +213,14 @@ def _apply_global_transform( # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-aws-secretsmanager.html LOG.warning("%s is not yet supported. Ignoring.", SECRETSMANAGER_TRANSFORM) transformed_template = template + elif transform_name == INCLUDE_TRANSFORM: + transformed_template = self._apply_global_include( + global_transform=global_transform, + region_name=self._change_set.region_name, + account_id=self._change_set.account_id, + template=template, + parameters=parameters, + ) else: transformed_template = self._apply_global_macro_transformation( account_id=self._change_set.account_id, @@ -213,11 +252,12 @@ def transform(self) -> tuple[dict, dict]: if not transformed_before_template: transformed_before_template = self._before_template for before_global_transform in transform_before: - transformed_before_template = self._apply_global_transform( - global_transform=before_global_transform, - parameters=parameters_before, - template=transformed_before_template, - ) + if not is_nothing(before_global_transform.name): + transformed_before_template = self._apply_global_transform( + global_transform=before_global_transform, + parameters=parameters_before, + template=transformed_before_template, + ) self._before_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_before_template transformed_after_template = self._after_template @@ -226,11 +266,12 @@ def transform(self) -> tuple[dict, dict]: if not transformed_after_template: transformed_after_template = self._after_template for after_global_transform in transform_after: - transformed_after_template = self._apply_global_transform( - global_transform=after_global_transform, - parameters=parameters_after, - template=transformed_after_template, - ) + if not is_nothing(after_global_transform.name): + transformed_after_template = self._apply_global_transform( + global_transform=after_global_transform, + parameters=parameters_after, + template=transformed_after_template, + ) self._after_cache[_SCOPE_TRANSFORM_TEMPLATE_OUTCOME] = transformed_after_template self._save_runtime_cache() @@ -257,8 +298,8 @@ def visit_node_transform( self, node_transform: NodeTransform ) -> PreprocEntityDelta[list[GlobalTransform], list[GlobalTransform]]: change_type = node_transform.change_type - before = list() if change_type != ChangeType.CREATED else Nothing - after = list() if change_type != ChangeType.REMOVED else Nothing + before = [] if change_type != ChangeType.CREATED else Nothing + after = [] if change_type != ChangeType.REMOVED else Nothing for change_set_entity in node_transform.global_transforms: delta: PreprocEntityDelta[GlobalTransform, GlobalTransform] = self.visit( change_set_entity=change_set_entity diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py index 8176733b44667..903638b7c571a 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_validator.py @@ -1,53 +1,141 @@ import re +from typing import Any from localstack.services.cloudformation.engine.v2.change_set_model import ( - NodeParameters, - NodeResource, + Maybe, + NodeIntrinsicFunction, NodeTemplate, + Nothing, is_nothing, ) from localstack.services.cloudformation.engine.v2.change_set_model_preproc import ( + _PSEUDO_PARAMETERS, + ChangeSetModelPreproc, PreprocEntityDelta, + PreprocResource, ) -from localstack.services.cloudformation.engine.v2.change_set_model_visitor import ( - ChangeSetModelVisitor, -) -from localstack.services.cloudformation.engine.validations import ValidationError -from localstack.services.cloudformation.v2.entities import ChangeSet - -VALID_LOGICAL_RESOURCE_ID_RE = re.compile(r"^[A-Za-z0-9]+$") -class ChangeSetModelValidator(ChangeSetModelVisitor): - def __init__(self, change_set: ChangeSet): - self._change_set = change_set - +class ChangeSetModelValidator(ChangeSetModelPreproc): def validate(self): - self.visit(self._change_set.update_model.node_template) + self.process() def visit_node_template(self, node_template: NodeTemplate): - self.visit(node_template.parameters) + self.visit(node_template.mappings) self.visit(node_template.resources) - def visit_node_parameters(self, node_parameters: NodeParameters) -> PreprocEntityDelta: - # check that all parameters have values - invalid_parameters = [] - for node_parameter in node_parameters.parameters: - self.visit(node_parameter) - if is_nothing(node_parameter.default_value.value) and is_nothing( - node_parameter.dynamic_value.value + def visit_node_intrinsic_function_fn_get_att( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + arguments_delta = self.visit(node_intrinsic_function.arguments) + before_arguments: Maybe[str | list[str]] = arguments_delta.before + after_arguments: Maybe[str | list[str]] = arguments_delta.after + + before = self._before_cache.get(node_intrinsic_function.scope, Nothing) + if is_nothing(before) and not is_nothing(before_arguments): + before = ".".join(before_arguments) + + after = self._after_cache.get(node_intrinsic_function.scope, Nothing) + if is_nothing(after) and not is_nothing(after_arguments): + after = ".".join(after_arguments) + + return PreprocEntityDelta(before=before, after=after) + + def visit_node_intrinsic_function_fn_sub( + self, node_intrinsic_function: NodeIntrinsicFunction + ) -> PreprocEntityDelta: + def _compute_sub(args: str | list[Any], select_before: bool) -> str: + # TODO: add further schema validation. + string_template: str + sub_parameters: dict + if isinstance(args, str): + string_template = args + sub_parameters = {} + elif ( + isinstance(args, list) + and len(args) == 2 + and isinstance(args[0], str) + and isinstance(args[1], dict) ): - invalid_parameters.append(node_parameter.name) + string_template = args[0] + sub_parameters = args[1] + else: + raise RuntimeError( + "Invalid arguments shape for Fn::Sub, expected a String " + f"or a Tuple of String and Map but got '{args}'" + ) + sub_string = string_template + template_variable_names = re.findall("\\${([^}]+)}", string_template) + for template_variable_name in template_variable_names: + template_variable_value = Nothing + + # Try to resolve the variable name as pseudo parameter. + if template_variable_name in _PSEUDO_PARAMETERS: + template_variable_value = self._resolve_pseudo_parameter( + pseudo_parameter_name=template_variable_name + ) + + # Try to resolve the variable name as an entry to the defined parameters. + elif template_variable_name in sub_parameters: + template_variable_value = sub_parameters[template_variable_name] + + # Try to resolve the variable name as GetAtt. + elif "." in template_variable_name: + try: + template_variable_value = self._resolve_attribute( + arguments=template_variable_name, select_before=select_before + ) + except RuntimeError: + pass + + # Try to resolve the variable name as Ref. + else: + try: + resource_delta = self._resolve_reference(logical_id=template_variable_name) + template_variable_value = ( + resource_delta.before if select_before else resource_delta.after + ) + if isinstance(template_variable_value, PreprocResource): + template_variable_value = template_variable_value.physical_resource_id + except RuntimeError: + pass + + if is_nothing(template_variable_value): + # override the base method just for this line to prevent accessing the + # resource properties since we are not deploying any resources + template_variable_value = "" + + if not isinstance(template_variable_value, str): + template_variable_value = str(template_variable_value) - if invalid_parameters: - raise ValidationError(f"Parameters: [{','.join(invalid_parameters)}] must have values") + sub_string = sub_string.replace( + f"${{{template_variable_name}}}", template_variable_value + ) - # continue visiting - return super().visit_node_parameters(node_parameters) + # FIXME: the following type reduction is ported from v1; however it appears as though such + # reduction is not performed by the engine, and certainly not at this depth given the + # lack of context. This section should be removed with Fn::Sub always retuning a string + # and the resource providers reviewed. + account_id = self._change_set.account_id + is_another_account_id = sub_string.isdigit() and len(sub_string) == len(account_id) + if sub_string == account_id or is_another_account_id: + result = sub_string + elif sub_string.isdigit(): + result = int(sub_string) + else: + try: + result = float(sub_string) + except ValueError: + result = sub_string + return result - def visit_node_resource(self, node_resource: NodeResource) -> PreprocEntityDelta: - if not VALID_LOGICAL_RESOURCE_ID_RE.match(node_resource.name): - raise ValidationError( - f"Template format error: Resource name {node_resource.name} is non alphanumeric." - ) - return super().visit_node_resource(node_resource) + arguments_delta = self.visit(node_intrinsic_function.arguments) + arguments_before = arguments_delta.before + arguments_after = arguments_delta.after + before = self._before_cache.get(node_intrinsic_function.scope, Nothing) + if is_nothing(before) and not is_nothing(arguments_before): + before = _compute_sub(args=arguments_before, select_before=True) + after = self._after_cache.get(node_intrinsic_function.scope, Nothing) + if is_nothing(after) and not is_nothing(arguments_after): + after = _compute_sub(args=arguments_after, select_before=False) + return PreprocEntityDelta(before=before, after=after) diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py index 6333e9f8dbae2..882e8a8d58bc7 100644 --- a/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +++ b/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_visitor.py @@ -177,6 +177,11 @@ def visit_node_intrinsic_function_condition( ): self.visit_children(node_intrinsic_function) + def visit_node_intrinsic_function_fn_import_value( + self, node_intrinsic_function: NodeIntrinsicFunction + ): + self.visit_children(node_intrinsic_function) + def visit_node_divergence(self, node_divergence: NodeDivergence): self.visit_children(node_divergence) diff --git a/localstack-core/localstack/services/cloudformation/engine/v2/resolving.py b/localstack-core/localstack/services/cloudformation/engine/v2/resolving.py new file mode 100644 index 0000000000000..c8f95b72455de --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/engine/v2/resolving.py @@ -0,0 +1,100 @@ +import logging +import re +from dataclasses import dataclass +from typing import Any + +from botocore.exceptions import ClientError + +from localstack.aws.connect import connect_to +from localstack.utils import json + +LOG = logging.getLogger(__name__) + +REGEX_DYNAMIC_REF = re.compile(r"{{resolve:([^:]+):(.+)}}") + + +@dataclass +class DynamicReference: + service_name: str + reference_key: str + + +def extract_dynamic_reference(value: Any) -> DynamicReference | None: + if isinstance(value, str): + if dynamic_ref_match := REGEX_DYNAMIC_REF.match(value): + return DynamicReference(dynamic_ref_match[1], dynamic_ref_match[2]) + return None + + +def perform_dynamic_reference_lookup( + reference: DynamicReference, account_id: str, region_name: str +) -> str | None: + # basic dynamic reference support + # see: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html + # technically there are more restrictions for each of these services but checking each of these + # isn't really necessary for the current level of emulation + + # only these 3 services are supported for dynamic references right now + if reference.service_name == "ssm": + ssm_client = connect_to(aws_access_key_id=account_id, region_name=region_name).ssm + try: + return ssm_client.get_parameter(Name=reference.reference_key)["Parameter"]["Value"] + except ClientError as e: + LOG.error("client error accessing SSM parameter '%s': %s", reference.reference_key, e) + raise + elif reference.service_name == "ssm-secure": + ssm_client = connect_to(aws_access_key_id=account_id, region_name=region_name).ssm + try: + return ssm_client.get_parameter(Name=reference.reference_key, WithDecryption=True)[ + "Parameter" + ]["Value"] + except ClientError as e: + LOG.error("client error accessing SSM parameter '%s': %s", reference.reference_key, e) + raise + elif reference.service_name == "secretsmanager": + # reference key needs to be parsed further + # because {{resolve:secretsmanager:secret-id:secret-string:json-key:version-stage:version-id}} + # we match for "secret-id:secret-string:json-key:version-stage:version-id" + # where + # secret-id can either be the secret name or the full ARN of the secret + # secret-string *must* be SecretString + # all other values are optional + secret_id = reference.reference_key + [json_key, version_stage, version_id] = [None, None, None] + if "SecretString" in reference.reference_key: + parts = reference.reference_key.split(":SecretString:") + secret_id = parts[0] + # json-key, version-stage and version-id are optional. + [json_key, version_stage, version_id] = f"{parts[1]}::".split(":")[:3] + + kwargs = {} # optional args for get_secret_value + if version_id: + kwargs["VersionId"] = version_id + if version_stage: + kwargs["VersionStage"] = version_stage + + secretsmanager_client = connect_to( + aws_access_key_id=account_id, region_name=region_name + ).secretsmanager + try: + secret_value = secretsmanager_client.get_secret_value(SecretId=secret_id, **kwargs)[ + "SecretString" + ] + except ClientError: + LOG.error("client error while trying to access key '%s': %s", secret_id) + raise + + if json_key: + json_secret = json.loads(secret_value) + if json_key not in json_secret: + raise RuntimeError( + f"JSON value for {reference.service_name}.{reference.reference_key} not present" + ) + return json_secret[json_key] + else: + return secret_value + + LOG.warning( + "Unsupported service for dynamic parameter: service_name=%s", reference.service_name + ) + return None diff --git a/localstack-core/localstack/services/cloudformation/provider.py b/localstack-core/localstack/services/cloudformation/provider.py index e9dc163b6fb0c..06a4279555ab4 100644 --- a/localstack-core/localstack/services/cloudformation/provider.py +++ b/localstack-core/localstack/services/cloudformation/provider.py @@ -927,7 +927,7 @@ def list_exports( self, context: RequestContext, next_token: NextToken = None, **kwargs ) -> ListExportsOutput: state = get_cloudformation_store(context.account_id, context.region) - return ListExportsOutput(Exports=state.exports) + return ListExportsOutput(Exports=state.exports.values()) @handler("ListImports") def list_imports( diff --git a/localstack-core/localstack/services/cloudformation/provider_utils.py b/localstack-core/localstack/services/cloudformation/provider_utils.py index d7e3eb49b79f2..c87238a9ae86d 100644 --- a/localstack-core/localstack/services/cloudformation/provider_utils.py +++ b/localstack-core/localstack/services/cloudformation/provider_utils.py @@ -9,9 +9,9 @@ import json import re import uuid +from collections.abc import Callable from copy import deepcopy from pathlib import Path -from typing import Callable, List, Optional from botocore.model import Shape, StructureShape @@ -73,9 +73,12 @@ def recurse_properties(properties: dict, fn: Callable) -> dict: return _recurse_properties(deepcopy(properties), fn) -def keys_pascalcase_to_lower_camelcase(model: dict) -> dict: +def keys_pascalcase_to_lower_camelcase(model: dict, skip_keys: set = None) -> dict: """Recursively change any dicts keys to lower camelcase""" + if skip_keys: + return _pascal_to_camel_keys_preserve_values(model, skip_keys) + def _keys_pascalcase_to_lower_camelcase(obj): if isinstance(obj, dict): return {convert_pascalcase_to_lower_camelcase(k): v for k, v in obj.items()} @@ -85,6 +88,33 @@ def _keys_pascalcase_to_lower_camelcase(obj): return _recurse_properties(model, _keys_pascalcase_to_lower_camelcase) +def _pascal_to_camel_keys_preserve_values(model: dict, skip_keys: set = None) -> dict: + """ + Variant of keys_pascalcase_to_lower_camelcase + All VALUES of provided keys are skipped and not transformed to lower camelcase. + The keys themselves will be transformed. + The function simply stops recursion if a key matches, so make sure no lower level values are ignored. + """ + skip_keys = skip_keys or set() + + def _transform(obj): + if isinstance(obj, dict): + new_dict = {} + for k, v in obj.items(): + new_key = convert_pascalcase_to_lower_camelcase(k) + if k in skip_keys: + new_dict[new_key] = v + else: + new_dict[new_key] = _transform(v) + return new_dict + elif isinstance(obj, list): + return [_transform(i) for i in obj] + else: + return obj + + return _transform(model) + + def keys_lower_camelcase_to_pascalcase(model: dict) -> dict: """Recursively change any dicts keys to PascalCase""" @@ -152,7 +182,7 @@ def fix_boto_parameters_based_on_report(original_params: dict, report: str) -> d cast_class = getattr(builtins, valid_class) old_value = get_nested(params, param_name) - if cast_class == bool and str(old_value).lower() in ["true", "false"]: + if isinstance(cast_class, bool) and str(old_value).lower() in ["true", "false"]: new_value = str(old_value).lower() == "true" else: new_value = cast_class(old_value) @@ -213,7 +243,7 @@ def transform_value(value, member_shape): return transformed_dict -def convert_values_to_numbers(input_dict: dict, keys_to_skip: Optional[List[str]] = None): +def convert_values_to_numbers(input_dict: dict, keys_to_skip: list[str] | None = None): """ Recursively converts all string values that represent valid integers in a dictionary (including nested dictionaries and lists) to integers. diff --git a/localstack-core/localstack/services/cloudformation/resource_provider.py b/localstack-core/localstack/services/cloudformation/resource_provider.py index 421ad8ecd2b30..169b7c33a30da 100644 --- a/localstack-core/localstack/services/cloudformation/resource_provider.py +++ b/localstack-core/localstack/services/cloudformation/resource_provider.py @@ -5,11 +5,12 @@ import re import time import uuid +from collections.abc import Callable from dataclasses import dataclass, field from enum import Enum, auto from logging import Logger from math import ceil -from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Type, TypedDict, TypeVar +from typing import TYPE_CHECKING, Any, Generic, TypedDict, TypeVar import botocore from botocore.client import BaseClient @@ -51,7 +52,7 @@ Properties = TypeVar("Properties") -PUBLIC_REGISTRY: dict[str, Type[ResourceProvider]] = {} +PUBLIC_REGISTRY: dict[str, type[ResourceProvider]] = {} PROVIDER_DEFAULTS = {} # TODO: remove this after removing patching in -ext @@ -66,12 +67,12 @@ class OperationStatus(Enum): @dataclass class ProgressEvent(Generic[Properties]): status: OperationStatus - resource_model: Optional[Properties] = None - resource_models: Optional[list[Properties]] = None + resource_model: Properties | None = None + resource_models: list[Properties] | None = None message: str = "" - result: Optional[str] = None - error_code: Optional[str] = None # TODO: enum + result: str | None = None + error_code: str | None = None # TODO: enum custom_context: dict = field(default_factory=dict) @@ -84,7 +85,7 @@ class Credentials(TypedDict): class ResourceProviderPayloadRequestData(TypedDict): logicalResourceId: str resourceProperties: Properties - previousResourceProperties: Optional[Properties] + previousResourceProperties: Properties | None callerCredentials: Credentials providerCredentials: Credentials systemTags: dict[str, str] @@ -184,8 +185,8 @@ class ResourceRequest(Generic[Properties]): custom_context: dict = field(default_factory=dict) - previous_state: Optional[Properties] = None - previous_tags: Optional[dict[str, str]] = None + previous_state: Properties | None = None + previous_tags: dict[str, str] | None = None tags: dict[str, str] = field(default_factory=dict) @@ -235,7 +236,7 @@ def get_resource_type(resource: dict) -> str: LOG.warning( "Failed to retrieve resource type %s", resource.get("Type"), - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) @@ -465,15 +466,18 @@ def deploy_loop( "A ResourceProvider should always have a SCHEMA property defined." ) resource_type_schema = resource_provider.SCHEMA - physical_resource_id = self.extract_physical_resource_id_from_model_with_schema( - event.resource_model, - raw_payload["resourceType"], - resource_type_schema, - ) + if raw_payload["action"] != "Remove": + physical_resource_id = ( + self.extract_physical_resource_id_from_model_with_schema( + event.resource_model, + raw_payload["resourceType"], + resource_type_schema, + ) + ) - resource["PhysicalResourceId"] = physical_resource_id - resource["Properties"] = event.resource_model - resource["_last_deployed_state"] = copy.deepcopy(event.resource_model) + resource["PhysicalResourceId"] = physical_resource_id + resource["Properties"] = event.resource_model + resource["_last_deployed_state"] = copy.deepcopy(event.resource_model) return event case OperationStatus.IN_PROGRESS: # update the shared state @@ -573,7 +577,7 @@ def try_load_resource_provider(resource_type: str) -> ResourceProvider | None: LOG.warning( "Failed to load PRO resource type %s as a ResourceProvider.", resource_type, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) # 2. try to load community resource provider @@ -588,7 +592,7 @@ def try_load_resource_provider(resource_type: str) -> ResourceProvider | None: LOG.warning( "Failed to load community resource type %s as a ResourceProvider.", resource_type, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) # we could not find the resource provider diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py index 8f17b3d36368e..806f1df253e4d 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,12 +15,12 @@ class CloudFormationMacroProperties(TypedDict): - FunctionName: Optional[str] - Name: Optional[str] - Description: Optional[str] - Id: Optional[str] - LogGroupName: Optional[str] - LogRoleARN: Optional[str] + FunctionName: str | None + Name: str | None + Description: str | None + Id: str | None + LogGroupName: str | None + LogRoleARN: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py index 9c6572792fc21..76c615e9179f1 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_macro_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationMacroProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CloudFormation::Macro" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_macro import ( diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py index b30c629682cc6..1defe49ff3730 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class CloudFormationStackProperties(TypedDict): - TemplateURL: Optional[str] - Id: Optional[str] - NotificationARNs: Optional[list[str]] - Parameters: Optional[dict] - Tags: Optional[list[Tag]] - TimeoutInMinutes: Optional[int] + TemplateURL: str | None + Id: str | None + NotificationARNs: list[str] | None + Parameters: dict | None + Tags: list[Tag] | None + TimeoutInMinutes: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py index 9dc020a564aa4..17c604a09e06d 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_stack_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationStackProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CloudFormation::Stack" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_stack import ( diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py index 051c901e425d9..136bf77f6a1a2 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition.py @@ -3,7 +3,7 @@ import uuid from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,11 +15,11 @@ class CloudFormationWaitConditionProperties(TypedDict): - Count: Optional[int] - Data: Optional[dict] - Handle: Optional[str] - Id: Optional[str] - Timeout: Optional[str] + Count: int | None + Data: dict | None + Handle: str | None + Id: str | None + Timeout: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py index bdc8b49fd2e6d..75b2afe98483f 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitcondition_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationWaitConditionProviderPlugin(CloudFormationResourceProviderPl name = "AWS::CloudFormation::WaitCondition" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_waitcondition import ( diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py index f2b5237876fe0..60df870b9649e 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,7 +14,7 @@ class CloudFormationWaitConditionHandleProperties(TypedDict): - Id: Optional[str] + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py index f5888171517ab..13bafb3e8a0fc 100644 --- a/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py +++ b/localstack-core/localstack/services/cloudformation/resource_providers/aws_cloudformation_waitconditionhandle_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudFormationWaitConditionHandleProviderPlugin(CloudFormationResourceProv name = "AWS::CloudFormation::WaitConditionHandle" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudformation.resource_providers.aws_cloudformation_waitconditionhandle import ( diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py b/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py index 531eff44d611b..2ea55f0493542 100644 --- a/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py +++ b/localstack-core/localstack/services/cloudformation/scaffolding/__main__.py @@ -3,11 +3,12 @@ import json import os import zipfile +from collections.abc import Generator from dataclasses import dataclass from enum import Enum, auto from functools import reduce from pathlib import Path -from typing import Any, Generator, Literal, Optional, TypedDict, TypeVar +from typing import Any, Literal, TypedDict, TypeVar import click from jinja2 import Environment, FileSystemLoader @@ -40,12 +41,12 @@ def Syntax(text: str, *args, **kwargs) -> str: class Property(TypedDict): - type: Optional[Literal["str"]] - items: Optional[dict] + type: Literal["str"] | None + items: dict | None class HandlerDefinition(TypedDict): - permissions: Optional[list[str]] + permissions: list[str] | None class HandlersDefinition(TypedDict): @@ -58,8 +59,8 @@ class HandlersDefinition(TypedDict): class ResourceSchema(TypedDict): typeName: str - description: Optional[str] - required: Optional[list[str]] + description: str | None + required: list[str] | None properties: dict[str, Property] handlers: HandlersDefinition @@ -171,7 +172,7 @@ def tests_root_dir(pro: bool = False) -> Path: def template_path( resource_name: ResourceName, file_type: FileType, - root: Optional[Path] = None, + root: Path | None = None, pro: bool = False, ) -> Path: """ @@ -266,11 +267,11 @@ def render( # FileType.cloudcontrol_test: "test_cloudcontrol_template.py.j2", FileType.parity_test: "test_parity_template.py.j2", } - kwargs = dict( - name=resource_name.full_name, # AWS::SNS::Topic - resource=resource_name.provider_name(), # SNSTopic - scaffolding_version=f"v{SCAFFOLDING_VERSION}", - ) + kwargs = { + "name": resource_name.full_name, # AWS::SNS::Topic + "resource": resource_name.provider_name(), # SNSTopic + "scaffolding_version": f"v{SCAFFOLDING_VERSION}", + } # TODO: we might want to segregate each provider in its own directory # e.g. .../resource_providers/aws_iam_role/test_X.py vs. .../resource_providers/iam/test_X.py # add extra parameters diff --git a/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py b/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py index 6a7e90166b490..626f0a9b7a82e 100644 --- a/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py +++ b/localstack-core/localstack/services/cloudformation/scaffolding/propgen.py @@ -7,7 +7,7 @@ import logging import textwrap from dataclasses import dataclass -from typing import Optional, TypedDict +from typing import TypedDict LOG = logging.getLogger(__name__) @@ -86,7 +86,7 @@ class Schema(TypedDict): properties: dict definitions: dict typeName: str - required: Optional[list[str]] + required: list[str] | None TYPE_MAP = { diff --git a/localstack-core/localstack/services/cloudformation/stores.py b/localstack-core/localstack/services/cloudformation/stores.py index 01bcf61ebe14e..994c277c76305 100644 --- a/localstack-core/localstack/services/cloudformation/stores.py +++ b/localstack-core/localstack/services/cloudformation/stores.py @@ -1,7 +1,6 @@ import logging -from typing import Optional -from localstack.aws.api.cloudformation import StackStatus +from localstack.aws.api.cloudformation import Export, StackStatus from localstack.services.cloudformation.engine.entities import Stack, StackChangeSet, StackSet from localstack.services.cloudformation.v2.entities import ChangeSet as ChangeSetV2 from localstack.services.cloudformation.v2.entities import Stack as StackV2 @@ -25,31 +24,44 @@ class CloudFormationStore(BaseStore): # maps macro ID to macros macros: dict[str, dict] = LocalAttribute(default=dict) - # exports: dict[str, str] @property - def exports(self): - exports = [] - output_keys = {} + def exports(self) -> dict[str, Export]: + exports = {} + for stack_id, stack in self.stacks.items(): + if stack.status == StackStatus.DELETE_COMPLETE: + continue + for output in stack.resolved_outputs: export_name = output.get("ExportName") if not export_name: continue - if export_name in output_keys: + if export_name in exports.keys(): # TODO: raise exception on stack creation in case of duplicate exports LOG.warning( "Found duplicate export name %s in stacks: %s %s", export_name, - output_keys[export_name], + output["OutputValue"], stack.stack_id, ) - entry = { - "ExportingStackId": stack.stack_id, - "Name": export_name, - "Value": output["OutputValue"], - } - exports.append(entry) - output_keys[export_name] = stack.stack_id + exports[export_name] = Export( + ExportingStackId=stack.stack_id, Name=export_name, Value=output["OutputValue"] + ) + + return exports + + @property + def exports_v2(self) -> dict[str, Export]: + exports = {} + stacks_v2 = self.stacks_v2.values() + for stack in stacks_v2: + if stack.status == StackStatus.DELETE_COMPLETE: + continue + for export_name, export_value in stack.resolved_exports.items(): + exports[export_name] = Export( + ExportingStackId=stack.stack_id, Name=export_name, Value=export_value + ) + return exports @@ -114,9 +126,9 @@ def find_change_set( account_id: str, region_name: str, cs_name: str, - stack_name: Optional[str] = None, + stack_name: str | None = None, active_only: bool = False, -) -> Optional[StackChangeSet]: +) -> StackChangeSet | None: store = get_cloudformation_store(account_id, region_name) for stack in store.stacks.values(): if active_only and stack.status == StackStatus.DELETE_COMPLETE: @@ -128,9 +140,6 @@ def find_change_set( return None -def exports_map(account_id: str, region_name: str): - result = {} +def exports_map(account_id: str, region_name: str) -> dict[str, Export]: store = get_cloudformation_store(account_id, region_name) - for export in store.exports: - result[export["Name"]] = export - return result + return {**store.exports, **store.exports_v2} diff --git a/localstack-core/localstack/services/cloudformation/v2/entities.py b/localstack-core/localstack/services/cloudformation/v2/entities.py index 2c6cb12e082be..3c93a2bb10a72 100644 --- a/localstack-core/localstack/services/cloudformation/v2/entities.py +++ b/localstack-core/localstack/services/cloudformation/v2/entities.py @@ -1,6 +1,5 @@ -import copy -from datetime import datetime, timezone -from typing import NotRequired, Optional, TypedDict +from datetime import UTC, datetime +from typing import NotRequired, TypedDict from localstack.aws.api.cloudformation import ( Capability, @@ -10,11 +9,7 @@ CreateStackInput, CreateStackSetInput, ExecutionStatus, - Output, - Parameter, ResourceStatus, - StackDriftInformation, - StackDriftStatus, StackEvent, StackInstanceComprehensiveStatus, StackInstanceDetailedStatus, @@ -25,7 +20,7 @@ StackStatusReason, ) from localstack.aws.api.cloudformation import ( - Stack as ApiStack, + Parameter as ApiParameter, ) from localstack.services.cloudformation.engine.entities import ( StackIdentifier, @@ -34,22 +29,15 @@ ChangeType, UpdateModel, ) +from localstack.services.cloudformation.v2.types import ResolvedResource from localstack.utils.aws import arns from localstack.utils.strings import long_uid, short_uid -class ResolvedResource(TypedDict): - LogicalResourceId: str - Type: str - Properties: dict - ResourceStatus: ResourceStatus - PhysicalResourceId: str | None - LastUpdatedTimestamp: datetime | None - - class Stack: stack_name: str - parameters: list[Parameter] + description: str | None + parameters: list[ApiParameter] change_set_id: str | None status: StackStatus status_reason: StackStatusReason | None @@ -60,35 +48,33 @@ class Stack: capabilities: list[Capability] enable_termination_protection: bool processed_template: dict | None + template_body: str | None # state after deploy resolved_parameters: dict[str, str] resolved_resources: dict[str, ResolvedResource] resolved_outputs: dict[str, str] resource_states: dict[str, StackResource] + resolved_exports: dict[str, str] def __init__( self, account_id: str, region_name: str, request_payload: CreateChangeSetInput | CreateStackInput, - template: dict | None = None, - template_body: str | None = None, initial_status: StackStatus = StackStatus.CREATE_IN_PROGRESS, ): self.account_id = account_id self.region_name = region_name - self.template = template - self.template_original = copy.deepcopy(self.template) - self.template_body = template_body self.status = initial_status self.status_reason = None self.change_set_ids = [] - self.creation_time = datetime.now(tz=timezone.utc) + self.creation_time = datetime.now(tz=UTC) self.deletion_time = None self.change_set_id = None self.enable_termination_protection = False self.processed_template = None + self.template_body = None self.stack_name = request_payload["StackName"] self.parameters = request_payload.get("Parameters", []) @@ -111,13 +97,21 @@ def __init__( self.resolved_outputs = {} self.resource_states = {} self.events = [] + self.resolved_exports = {} + self.description = None def set_stack_status(self, status: StackStatus, reason: StackStatusReason | None = None): self.status = status if reason: self.status_reason = reason - self._store_event(self.stack_name, self.stack_id, status.value, status_reason=reason) + self._store_event( + resource_id=self.stack_name, + resource_type="AWS::CloudFormation::Stack", + physical_resource_id=self.stack_id, + status=status, + status_reason=reason, + ) def set_resource_status( self, @@ -134,7 +128,7 @@ def set_resource_status( LogicalResourceId=logical_resource_id, PhysicalResourceId=physical_resource_id, ResourceType=resource_type, - Timestamp=datetime.now(tz=timezone.utc), + Timestamp=datetime.now(tz=UTC), ResourceStatus=status, ResourceStatusReason=resource_status_reason, ) @@ -146,78 +140,39 @@ def set_resource_status( self.resource_states.pop(logical_resource_id) else: self.resource_states[logical_resource_id] = resource_description - self._store_event(logical_resource_id, physical_resource_id, status, resource_status_reason) + + self._store_event( + resource_id=logical_resource_id, + resource_type=resource_type, + physical_resource_id=physical_resource_id, + status=status, + status_reason=resource_status_reason, + ) def _store_event( self, resource_id: str = None, - physical_res_id: str = None, - status: str = "", + resource_type: str | None = "", + physical_resource_id: str = None, + status: StackStatus | ResourceStatus = "", status_reason: str = "", ): - resource_id = resource_id - physical_res_id = physical_res_id - resource_type = ( - self.template.get("Resources", {}) - .get(resource_id, {}) - .get("Type", "AWS::CloudFormation::Stack") + event = StackEvent( + EventId=long_uid(), + Timestamp=datetime.now(tz=UTC), + StackId=self.stack_id, + StackName=self.stack_name, + LogicalResourceId=resource_id, + PhysicalResourceId=physical_resource_id, + ResourceStatus=status, + ResourceType=resource_type, ) - event: StackEvent = { - "EventId": long_uid(), - "Timestamp": datetime.now(tz=timezone.utc), - "StackId": self.stack_id, - "StackName": self.stack_name, - "LogicalResourceId": resource_id, - "PhysicalResourceId": physical_res_id, - "ResourceStatus": status, - "ResourceType": resource_type, - } - if status_reason: event["ResourceStatusReason"] = status_reason self.events.insert(0, event) - def describe_details(self) -> ApiStack: - result = { - "CreationTime": self.creation_time, - "DeletionTime": self.deletion_time, - "StackId": self.stack_id, - "StackName": self.stack_name, - "StackStatus": self.status, - "StackStatusReason": self.status_reason, - # fake values - "DisableRollback": False, - "DriftInformation": StackDriftInformation( - StackDriftStatus=StackDriftStatus.NOT_CHECKED - ), - "EnableTerminationProtection": self.enable_termination_protection, - "LastUpdatedTime": self.creation_time, - "RollbackConfiguration": {}, - "Tags": [], - "NotificationARNs": [], - "Capabilities": self.capabilities, - "Parameters": self.parameters, - } - # TODO: confirm the logic for this - if change_set_id := self.change_set_id: - result["ChangeSetId"] = change_set_id - - if self.resolved_outputs: - describe_outputs = [] - for key, value in self.resolved_outputs.items(): - describe_outputs.append( - Output( - # TODO(parity): Description, ExportName - # TODO(parity): what happens on describe stack when the stack has not been deployed yet? - OutputKey=key, - OutputValue=value, - ) - ) - result["Outputs"] = describe_outputs - return result - def is_active(self) -> bool: return self.status != StackStatus.DELETE_COMPLETE @@ -231,34 +186,42 @@ class ChangeSet: change_set_name: str change_set_id: str change_set_type: ChangeSetType - update_model: Optional[UpdateModel] + update_model: UpdateModel | None status: ChangeSetStatus status_reason: str | None execution_status: ExecutionStatus creation_time: datetime + processed_template: dict | None + resolved_parameters: list[ApiParameter] + description: str | None def __init__( self, stack: Stack, request_payload: ChangeSetRequestPayload, + template_body: str, template: dict | None = None, ): self.stack = stack + self.template_body = template_body self.template = template self.status = ChangeSetStatus.CREATE_IN_PROGRESS self.status_reason = None self.execution_status = ExecutionStatus.AVAILABLE self.update_model = None - self.creation_time = datetime.now(tz=timezone.utc) + self.creation_time = datetime.now(tz=UTC) + self.resolved_parameters = [] self.change_set_name = request_payload["ChangeSetName"] self.change_set_type = request_payload.get("ChangeSetType", ChangeSetType.UPDATE) + self.description = request_payload.get("Description") self.change_set_id = arns.cloudformation_change_set_arn( self.change_set_name, change_set_id=short_uid(), account_id=self.stack.account_id, region_name=self.stack.region_name, ) + self.processed_template = None def set_update_model(self, update_model: UpdateModel) -> None: self.update_model = update_model diff --git a/localstack-core/localstack/services/cloudformation/v2/provider.py b/localstack-core/localstack/services/cloudformation/v2/provider.py index ce59205461547..916172e251ec8 100644 --- a/localstack-core/localstack/services/cloudformation/v2/provider.py +++ b/localstack-core/localstack/services/cloudformation/v2/provider.py @@ -2,11 +2,12 @@ import json import logging from collections import defaultdict -from datetime import datetime, timezone -from typing import Any, Optional +from datetime import UTC, datetime +from localstack import config from localstack.aws.api import RequestContext, handler from localstack.aws.api.cloudformation import ( + AlreadyExistsException, CallAs, Changes, ChangeSetNameOrId, @@ -43,6 +44,7 @@ IncludePropertyValues, InsufficientCapabilitiesException, InvalidChangeSetStatusException, + ListExportsOutput, ListStackResourcesOutput, ListStacksOutput, LogicalResourceId, @@ -53,6 +55,8 @@ RetainResources, RoleARN, RollbackConfiguration, + StackDriftInformation, + StackDriftStatus, StackName, StackNameOrId, StackResourceDetail, @@ -69,9 +73,13 @@ UpdateStackOutput, UpdateTerminationProtectionOutput, ) +from localstack.aws.api.cloudformation import ( + Stack as ApiStack, +) from localstack.aws.connect import connect_to from localstack.services.cloudformation import api_utils from localstack.services.cloudformation.engine import template_preparer +from localstack.services.cloudformation.engine.parameters import resolve_ssm_parameter from localstack.services.cloudformation.engine.v2.change_set_model import ( ChangeSetModel, ChangeType, @@ -100,7 +108,13 @@ CloudFormationStore, get_cloudformation_store, ) -from localstack.services.cloudformation.v2.entities import ChangeSet, Stack, StackInstance, StackSet +from localstack.services.cloudformation.v2.entities import ( + ChangeSet, + Stack, + StackInstance, + StackSet, +) +from localstack.services.cloudformation.v2.types import EngineParameter from localstack.utils.collections import select_attributes from localstack.utils.strings import short_uid from localstack.utils.threads import start_worker_thread @@ -159,7 +173,7 @@ def find_change_set_v2( state: CloudFormationStore, change_set_name: str, stack_name: str | None = None ) -> ChangeSet | None: if is_changeset_arn(change_set_name): - return state.change_sets[change_set_name] + return state.change_sets.get(change_set_name) else: if stack_name is not None: stack = find_stack_v2(state, stack_name) @@ -171,7 +185,9 @@ def find_change_set_v2( if change_set_candidate.change_set_name == change_set_name: return change_set_candidate else: - raise ValueError("No stack name specified when finding change set") + raise ValidationError( + "StackName must be specified if ChangeSetName is not specified as an ARN." + ) def find_stack_set_v2(state: CloudFormationStore, stack_set_name: str) -> StackSet | None: @@ -194,20 +210,77 @@ def find_stack_instance(stack_set: StackSet, account: str, region: str) -> Stack class CloudformationProviderV2(CloudformationProvider): @staticmethod + def _resolve_parameters( + template: dict | None, parameters: dict | None, account_id: str, region_name: str + ) -> dict[str, EngineParameter]: + template_parameters = template.get("Parameters", {}) + resolved_parameters = {} + invalid_parameters = [] + for name, parameter in template_parameters.items(): + given_value = parameters.get(name) + default_value = parameter.get("Default") + resolved_parameter = EngineParameter( + type_=parameter["Type"], given_value=given_value, default_value=default_value + ) + + if parameter["Type"] == "AWS::SSM::Parameter::Value": + # TODO: support other parameter types + try: + resolved_parameter["resolved_value"] = resolve_ssm_parameter( + account_id, region_name, given_value or default_value + ) + except Exception: + raise ValidationError( + f"Parameter {name} should either have input value or default value" + ) + elif given_value is None and default_value is None: + invalid_parameters.append(name) + continue + + resolved_parameters[name] = resolved_parameter + + if invalid_parameters: + raise ValidationError(f"Parameters: [{','.join(invalid_parameters)}] must have values") + + for name, parameter in resolved_parameters.items(): + if ( + parameter.get("resolved_value") is None + and parameter.get("given_value") is None + and parameter.get("default_value") is None + ): + raise ValidationError( + f"Parameter {name} should either have input value or default value" + ) + + return resolved_parameters + + @classmethod def _setup_change_set_model( + cls, change_set: ChangeSet, - before_template: Optional[dict], - after_template: Optional[dict], - before_parameters: Optional[dict], - after_parameters: Optional[dict], - previous_update_model: Optional[UpdateModel], + before_template: dict | None, + after_template: dict | None, + before_parameters: dict | None, + after_parameters: dict | None, + previous_update_model: UpdateModel | None, ): + resolved_parameters = None + if after_parameters is not None: + resolved_parameters = cls._resolve_parameters( + after_template, + after_parameters, + change_set.stack.account_id, + change_set.stack.region_name, + ) + + change_set.resolved_parameters = resolved_parameters + # Create and preprocess the update graph for this template update. change_set_model = ChangeSetModel( before_template=before_template, after_template=after_template, before_parameters=before_parameters, - after_parameters=after_parameters, + after_parameters=resolved_parameters, ) raw_update_model: UpdateModel = change_set_model.get_update_model() # If there exists an update model which operated in the 'before' version of this change set, @@ -222,7 +295,7 @@ def _setup_change_set_model( change_set_model_transform = ChangeSetModelTransform( change_set=change_set, before_parameters=before_parameters, - after_parameters=after_parameters, + after_parameters=resolved_parameters, before_template=before_template, after_template=after_template, ) @@ -235,7 +308,7 @@ def _setup_change_set_model( before_template=transformed_before_template, after_template=transformed_after_template, before_parameters=before_parameters, - after_parameters=after_parameters, + after_parameters=resolved_parameters, ) update_model = change_set_model.get_update_model() # Bring the cache for the previous operations forward in the update graph for this version @@ -252,7 +325,7 @@ def _setup_change_set_model( validator.validate() change_set.set_update_model(update_model) - change_set.stack.processed_template = transformed_after_template + change_set.processed_template = transformed_after_template @handler("CreateChangeSet", expand=False) def create_change_set( @@ -309,8 +382,6 @@ def create_change_set( account_id=context.account_id, region_name=context.region, request_payload=request, - template=structured_template, - template_body=template_body, initial_status=StackStatus.REVIEW_IN_PROGRESS, ) state.stacks_v2[stack.stack_id] = stack @@ -352,12 +423,9 @@ def create_change_set( # The options might be reduce to using the current style, or passing the extra information # as a metadata object. The choice should be made considering when the extra information # is needed for the update graph building, or only looked up in downstream tasks (metadata). - request_parameters = request.get("Parameters", list()) + request_parameters = request.get("Parameters", []) # TODO: handle parameter defaults and resolution - after_parameters: dict[str, Any] = { - parameter["ParameterKey"]: parameter["ParameterValue"] - for parameter in request_parameters - } + after_parameters = self._extract_after_parameters(request_parameters, before_parameters) # TODO: update this logic to always pass the clean template object if one exists. The # current issue with relaying on stack.template_original is that this appears to have @@ -377,7 +445,7 @@ def create_change_set( pass # create change set for the stack and apply changes - change_set = ChangeSet(stack, request, template=after_template) + change_set = ChangeSet(stack, request, template=after_template, template_body=template_body) self._setup_change_set_model( change_set=change_set, before_template=before_template, @@ -400,7 +468,6 @@ def create_change_set( change_set.set_change_set_status(ChangeSetStatus.CREATE_COMPLETE) - stack.change_set_id = change_set.change_set_id stack.change_set_ids.append(change_set.change_set_id) state.change_sets[change_set.change_set_id] = change_set @@ -457,14 +524,28 @@ def _run(*args): change_set.stack.set_stack_status(new_stack_status) change_set.set_execution_status(ExecutionStatus.EXECUTE_COMPLETE) change_set.stack.resolved_resources = result.resources - change_set.stack.resolved_parameters = result.parameters + change_set.stack.resolved_parameters = change_set.resolved_parameters change_set.stack.resolved_outputs = result.outputs + + change_set.stack.resolved_exports = {} + for output in result.outputs: + if export_name := output.get("ExportName"): + change_set.stack.resolved_exports[export_name] = output["OutputValue"] + + change_set.stack.change_set_id = change_set.change_set_id + change_set.stack.change_set_ids.append(change_set.change_set_id) + # if the deployment succeeded, update the stack's template representation to that # which was just deployed change_set.stack.template = change_set.template + change_set.stack.description = change_set.template.get("Description") + change_set.stack.processed_template = change_set.processed_template + change_set.stack.template_body = change_set.template_body except Exception as e: LOG.error( - "Execute change set failed: %s", e, exc_info=LOG.isEnabledFor(logging.WARNING) + "Execute change set failed: %s", + e, + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) new_stack_status = StackStatus.UPDATE_FAILED if change_set.change_set_type == ChangeSetType.CREATE: @@ -472,6 +553,8 @@ def _run(*args): change_set.stack.set_stack_status(new_stack_status) change_set.set_execution_status(ExecutionStatus.EXECUTE_FAILED) + change_set.stack.change_set_id = change_set.change_set_id + change_set.stack.change_set_ids.append(change_set.change_set_id) start_worker_thread(_run) @@ -501,15 +584,20 @@ def _describe_change_set( StackId=change_set.stack.stack_id, StackName=change_set.stack.stack_name, CreationTime=change_set.creation_time, - Parameters=[ - # TODO: add masking support. - Parameter(ParameterKey=key, ParameterValue=value) - for (key, value) in change_set.stack.resolved_parameters.items() - ], Changes=changes, Capabilities=change_set.stack.capabilities, StatusReason=change_set.status_reason, + Description=change_set.description, + # TODO: static information + IncludeNestedStacks=False, + NotificationARNs=[], ) + if change_set.resolved_parameters: + result["Parameters"] = [ + # TODO: add masking support. + Parameter(ParameterKey=key, ParameterValue=value) + for (key, value) in change_set.resolved_parameters.items() + ] return result @handler("DescribeChangeSet") @@ -543,16 +631,7 @@ def delete_change_set( **kwargs, ) -> DeleteChangeSetOutput: state = get_cloudformation_store(context.account_id, context.region) - - if is_changeset_arn(change_set_name): - change_set = state.change_sets.get(change_set_name) - elif not is_changeset_arn(change_set_name) and stack_name: - change_set = find_change_set_v2(state, change_set_name, stack_name) - else: - raise ValidationError( - "StackName must be specified if ChangeSetName is not specified as an ARN." - ) - + change_set = find_change_set_v2(state, change_set_name, stack_name) if not change_set: return DeleteChangeSetOutput() @@ -570,6 +649,26 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr raise ValidationError("StackName must be specified") state = get_cloudformation_store(context.account_id, context.region) + + active_stack_candidates = [ + stack + for stack in state.stacks_v2.values() + if stack.stack_name == stack_name and stack.status not in [StackStatus.DELETE_COMPLETE] + ] + + # TODO: fix/implement this code path + # this needs more investigation how Cloudformation handles it (e.g. normal stack create or does it create a separate changeset?) + # REVIEW_IN_PROGRESS is another special status + # in this case existing changesets are set to obsolete and the stack is created + # review_stack_candidates = [s for s in stack_candidates if s.status == StackStatus.REVIEW_IN_PROGRESS] + # if review_stack_candidates: + # set changesets to obsolete + # for cs in review_stack_candidates[0].change_sets: + # cs.execution_status = ExecutionStatus.OBSOLETE + + if active_stack_candidates: + raise AlreadyExistsException(f"Stack [{stack_name}] already exists") + # TODO: copied from create_change_set, consider unifying template_body = request.get("TemplateBody") # s3 or secretsmanager url @@ -600,8 +699,6 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr account_id=context.account_id, region_name=context.region, request_payload=request, - template=structured_template, - template_body=template_body, ) # TODO: what is the correct initial status? state.stacks_v2[stack.stack_id] = stack @@ -610,12 +707,9 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr # The options might be reduce to using the current style, or passing the extra information # as a metadata object. The choice should be made considering when the extra information # is needed for the update graph building, or only looked up in downstream tasks (metadata). - request_parameters = request.get("Parameters", list()) + request_parameters = request.get("Parameters", []) # TODO: handle parameter defaults and resolution - after_parameters: dict[str, Any] = { - parameter["ParameterKey"]: parameter["ParameterValue"] - for parameter in request_parameters - } + after_parameters = self._extract_after_parameters(request_parameters) after_template = structured_template # Create internal change set to execute @@ -623,6 +717,7 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr stack, {"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE}, template=after_template, + template_body=template_body, ) self._setup_change_set_model( change_set=change_set, @@ -642,14 +737,22 @@ def _run(*args): result = change_set_executor.execute() stack.set_stack_status(StackStatus.CREATE_COMPLETE) stack.resolved_resources = result.resources - stack.resolved_parameters = result.parameters stack.resolved_outputs = result.outputs # if the deployment succeeded, update the stack's template representation to that # which was just deployed stack.template = change_set.template + stack.template_body = change_set.template_body + stack.resolved_parameters = change_set.resolved_parameters + stack.resolved_exports = {} + for output in result.outputs: + if export_name := output.get("ExportName"): + stack.resolved_exports[export_name] = output["OutputValue"] + stack.processed_template = change_set.processed_template except Exception as e: LOG.error( - "Create Stack set failed: %s", e, exc_info=LOG.isEnabledFor(logging.WARNING) + "Create Stack set failed: %s", + e, + exc_info=LOG.isEnabledFor(logging.WARNING) and config.CFN_VERBOSE_ERRORS, ) stack.set_stack_status(StackStatus.CREATE_FAILED) @@ -676,11 +779,64 @@ def describe_stacks( **kwargs, ) -> DescribeStacksOutput: state = get_cloudformation_store(context.account_id, context.region) - stack = find_stack_v2(state, stack_name) - if not stack: - raise StackNotFoundError(stack_name) - # TODO: move describe_details method to provider - return DescribeStacksOutput(Stacks=[stack.describe_details()]) + if stack_name: + stack = find_stack_v2(state, stack_name) + if not stack: + raise ValidationError(f"Stack with id {stack_name} does not exist") + stacks = [stack] + else: + stacks = state.stacks_v2.values() + + describe_stack_output: list[ApiStack] = [] + for stack in stacks: + describe_stack_output.append(self._describe_stack(stack)) + + return DescribeStacksOutput(Stacks=describe_stack_output) + + @staticmethod + def _describe_stack(stack: Stack) -> ApiStack: + stack_description = ApiStack( + Description=stack.description, + CreationTime=stack.creation_time, + DeletionTime=stack.deletion_time, + StackId=stack.stack_id, + StackName=stack.stack_name, + StackStatus=stack.status, + StackStatusReason=stack.status_reason, + # fake values + DisableRollback=False, + DriftInformation=StackDriftInformation(StackDriftStatus=StackDriftStatus.NOT_CHECKED), + EnableTerminationProtection=stack.enable_termination_protection, + RollbackConfiguration=RollbackConfiguration(), + Tags=[], + NotificationARNs=[], + # "Parameters": stack.resolved_parameters, + ) + if stack.status != StackStatus.REVIEW_IN_PROGRESS: + # TODO: actually track updated time + stack_description["LastUpdatedTime"] = stack.creation_time + if stack.capabilities: + stack_description["Capabilities"] = stack.capabilities + # TODO: confirm the logic for this + if change_set_id := stack.change_set_id: + stack_description["ChangeSetId"] = change_set_id + + if stack.resolved_parameters: + stack_description["Parameters"] = [] + for name, resolved_parameter in stack.resolved_parameters.items(): + parameter = Parameter( + ParameterKey=name, + ParameterValue=resolved_parameter.get("given_value") + or resolved_parameter.get("default_value"), + ) + if resolved_value := resolved_parameter.get("resolved_value"): + parameter["ResolvedValue"] = resolved_value + stack_description["Parameters"].append(parameter) + + if stack.resolved_outputs: + stack_description["Outputs"] = stack.resolved_outputs + + return stack_description @handler("ListStacks") def list_stacks( @@ -693,7 +849,7 @@ def list_stacks( state = get_cloudformation_store(context.account_id, context.region) stacks = [ - s.describe_details() + self._describe_stack(s) for s in state.stacks_v2.values() if not stack_status_filter or s.status in stack_status_filter ] @@ -1172,12 +1328,10 @@ def update_stack( # The options might be reduce to using the current style, or passing the extra information # as a metadata object. The choice should be made considering when the extra information # is needed for the update graph building, or only looked up in downstream tasks (metadata). - request_parameters = request.get("Parameters", list()) + request_parameters = request.get("Parameters", []) # TODO: handle parameter defaults and resolution - after_parameters: dict[str, Any] = { - parameter["ParameterKey"]: parameter["ParameterValue"] - for parameter in request_parameters - } + after_parameters = self._extract_after_parameters(request_parameters, before_parameters) + before_template = stack.template after_template = structured_template @@ -1189,6 +1343,7 @@ def update_stack( change_set = ChangeSet( stack, {"ChangeSetName": f"cs-{stack_name}-create", "ChangeSetType": ChangeSetType.CREATE}, + template_body=template_body, template=after_template, ) self._setup_change_set_model( @@ -1214,20 +1369,51 @@ def _run(*args): result = change_set_executor.execute() stack.set_stack_status(StackStatus.UPDATE_COMPLETE) stack.resolved_resources = result.resources - stack.resolved_parameters = result.parameters stack.resolved_outputs = result.outputs # if the deployment succeeded, update the stack's template representation to that # which was just deployed stack.template = change_set.template + stack.template_body = change_set.template_body + stack.resolved_parameters = change_set.resolved_parameters + stack.resolved_exports = {} + for output in result.outputs: + if export_name := output.get("ExportName"): + stack.resolved_exports[export_name] = output["OutputValue"] except Exception as e: - LOG.error("Update Stack failed: %s", e, exc_info=LOG.isEnabledFor(logging.WARNING)) + LOG.error( + "Update Stack failed: %s", + e, + exc_info=LOG.isEnabledFor(logging.WARNING) and config.CFN_VERBOSE_ERRORS, + ) stack.set_stack_status(StackStatus.UPDATE_FAILED) start_worker_thread(_run) - # TODO: stack id return UpdateStackOutput(StackId=stack.stack_id) + @staticmethod + def _extract_after_parameters( + request_parameters, before_parameters: dict[str, str] | None = None + ) -> dict[str, str]: + before_parameters = before_parameters or {} + after_parameters = {} + for parameter in request_parameters: + key = parameter["ParameterKey"] + if parameter.get("UsePreviousValue", False): + # todo: what if the parameter does not exist in the before parameters + before = before_parameters[key] + after_parameters[key] = ( + before.get("resolved_value") + or before.get("given_value") + or before.get("default_value") + ) + continue + + if "ParameterValue" in parameter: + after_parameters[key] = parameter["ParameterValue"] + continue + return after_parameters + @handler("DeleteStack") def delete_stack( self, @@ -1249,7 +1435,7 @@ def delete_stack( # created, but never executed if stack.status == StackStatus.REVIEW_IN_PROGRESS and not stack.resolved_resources: stack.set_stack_status(StackStatus.DELETE_COMPLETE) - stack.deletion_time = datetime.now(tz=timezone.utc) + stack.deletion_time = datetime.now(tz=UTC) return previous_update_model = None @@ -1258,7 +1444,9 @@ def delete_stack( previous_update_model = previous_change_set.update_model # create a dummy change set - change_set = ChangeSet(stack, {"ChangeSetName": f"delete-stack_{stack.stack_name}"}) # noqa + change_set = ChangeSet( + stack, {"ChangeSetName": f"delete-stack_{stack.stack_name}"}, template_body="" + ) # noqa self._setup_change_set_model( change_set=change_set, before_template=stack.template, @@ -1275,14 +1463,21 @@ def _run(*args): stack.set_stack_status(StackStatus.DELETE_IN_PROGRESS) change_set_executor.execute() stack.set_stack_status(StackStatus.DELETE_COMPLETE) - stack.deletion_time = datetime.now(tz=timezone.utc) + stack.deletion_time = datetime.now(tz=UTC) except Exception as e: LOG.warning( "Failed to delete stack '%s': %s", stack.stack_name, e, - exc_info=LOG.isEnabledFor(logging.DEBUG), + exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS, ) stack.set_stack_status(StackStatus.DELETE_FAILED) start_worker_thread(_run) + + @handler("ListExports") + def list_exports( + self, context: RequestContext, next_token: NextToken = None, **kwargs + ) -> ListExportsOutput: + store = get_cloudformation_store(account_id=context.account_id, region_name=context.region) + return ListExportsOutput(Exports=store.exports_v2.values()) diff --git a/localstack-core/localstack/services/cloudformation/v2/types.py b/localstack-core/localstack/services/cloudformation/v2/types.py new file mode 100644 index 0000000000000..ec5159668c493 --- /dev/null +++ b/localstack-core/localstack/services/cloudformation/v2/types.py @@ -0,0 +1,32 @@ +from datetime import datetime +from typing import NotRequired, TypedDict + +from localstack.aws.api.cloudformation import ResourceStatus + + +class EngineParameter(TypedDict): + """ + Parameters supplied by the user. The resolved value field is populated by the engine + """ + + type_: str + given_value: NotRequired[str | None] + resolved_value: NotRequired[str | None] + default_value: NotRequired[str | None] + + +def engine_parameter_value(parameter: EngineParameter) -> str: + value = parameter.get("given_value") or parameter.get("default_value") + if value is None: + raise RuntimeError("Parameter value is None") + + return value + + +class ResolvedResource(TypedDict): + LogicalResourceId: str + Type: str + Properties: dict + ResourceStatus: ResourceStatus + PhysicalResourceId: str | None + LastUpdatedTimestamp: datetime | None diff --git a/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py b/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py index 2b0675f121450..5300038bbc5c5 100644 --- a/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py +++ b/localstack-core/localstack/services/cloudwatch/alarm_scheduler.py @@ -2,8 +2,8 @@ import logging import math import threading -from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING, List, Optional +from datetime import UTC, datetime, timedelta +from typing import TYPE_CHECKING from localstack.aws.api.cloudwatch import MetricAlarm, MetricDataQuery, MetricStat, StateValue from localstack.aws.connect import connect_to @@ -120,7 +120,7 @@ def _is_alarm_supported(self, alarm_details: MetricAlarm) -> bool: return True -def get_metric_alarm_details_for_alarm_arn(alarm_arn: str) -> Optional[MetricAlarm]: +def get_metric_alarm_details_for_alarm_arn(alarm_arn: str) -> MetricAlarm | None: alarm_name = arns.extract_resource_from_arn(alarm_arn).split(":", 1)[1] client = get_cloudwatch_client_for_region_of_alarm(alarm_arn) metric_alarms = client.describe_alarms(AlarmNames=[alarm_name])["MetricAlarms"] @@ -155,7 +155,7 @@ def generate_metric_query(alarm_details: MetricAlarm) -> MetricDataQuery: ) -def is_threshold_exceeded(metric_values: List[float], alarm_details: MetricAlarm) -> bool: +def is_threshold_exceeded(metric_values: list[float], alarm_details: MetricAlarm) -> bool: """Evaluates if the threshold is exceeded for the configured alarm and given metric values :param metric_values: values to compare against threshold @@ -185,7 +185,7 @@ def is_threshold_exceeded(metric_values: List[float], alarm_details: MetricAlarm return False -def is_triggering_premature_alarm(metric_values: List[float], alarm_details: MetricAlarm) -> bool: +def is_triggering_premature_alarm(metric_values: list[float], alarm_details: MetricAlarm) -> bool: """ Checks if a premature alarm should be triggered. https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#CloudWatch-alarms-avoiding-premature-transition: @@ -217,7 +217,7 @@ def is_triggering_premature_alarm(metric_values: List[float], alarm_details: Met return False -def collect_metric_data(alarm_details: MetricAlarm, client: "CloudWatchClient") -> List[float]: +def collect_metric_data(alarm_details: MetricAlarm, client: "CloudWatchClient") -> list[float]: """ Collects the metric data for the evaluation interval. @@ -236,7 +236,7 @@ def collect_metric_data(alarm_details: MetricAlarm, client: "CloudWatchClient") magic_number = max(math.floor(evaluation_periods / 3), 2) collected_periods = evaluation_periods + magic_number - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) metric_query = generate_metric_query(alarm_details) # get_metric_data needs to be run in a loop, so we also collect empty data points on the right position diff --git a/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py b/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py index 43383cf2782ad..63bd8c691819e 100644 --- a/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py +++ b/localstack-core/localstack/services/cloudwatch/cloudwatch_database_helper.py @@ -2,8 +2,7 @@ import os import sqlite3 import threading -from datetime import datetime, timezone -from typing import Dict, List, Optional +from datetime import UTC, datetime from localstack import config from localstack.aws.api.cloudwatch import MetricData, MetricDataQuery, ScanBy @@ -92,7 +91,7 @@ def add_metric_data( self, account_id: str, region: str, namespace: str, metric_data: MetricData ): def _get_current_unix_timestamp_utc(): - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) return int(now.timestamp()) for metric in metric_data: @@ -218,7 +217,7 @@ def get_metric_data_stat( start_time: datetime, end_time: datetime, scan_by: str, - ) -> Dict[str, List]: + ) -> dict[str, list]: metric_stat = query.get("MetricStat") metric = metric_stat.get("Metric") period = metric_stat.get("Period") @@ -389,7 +388,7 @@ def clear_tables(self): cur.execute("VACUUM") conn.commit() - def _get_ordered_dimensions_with_separator(self, dims: Optional[List[Dict]], for_search=False): + def _get_ordered_dimensions_with_separator(self, dims: list[dict] | None, for_search=False): """ Returns a string with the dimensions in the format "Name=Value\tName=Value\tName=Value" in order to store the metric with the dimensions in a single column in the database diff --git a/localstack-core/localstack/services/cloudwatch/models.py b/localstack-core/localstack/services/cloudwatch/models.py index a1246569f4f97..72e4e0ffc5313 100644 --- a/localstack-core/localstack/services/cloudwatch/models.py +++ b/localstack-core/localstack/services/cloudwatch/models.py @@ -1,6 +1,4 @@ import datetime -from datetime import timezone -from typing import Dict, List from localstack.aws.api.cloudwatch import CompositeAlarm, DashboardBody, MetricAlarm, StateValue from localstack.services.stores import ( @@ -25,7 +23,7 @@ def __init__(self, account_id: str, region: str, alarm: MetricAlarm): self.set_default_attributes() def set_default_attributes(self): - current_time = datetime.datetime.now(timezone.utc) + current_time = datetime.datetime.now(datetime.UTC) self.alarm["AlarmArn"] = arns.cloudwatch_alarm_arn( self.alarm["AlarmName"], account_id=self.account_id, region_name=self.region ) @@ -53,7 +51,7 @@ def __init__(self, account_id: str, region: str, alarm: CompositeAlarm): self.set_default_attributes() def set_default_attributes(self): - current_time = datetime.datetime.now(timezone.utc) + current_time = datetime.datetime.now(datetime.UTC) self.alarm["AlarmArn"] = arns.cloudwatch_alarm_arn( self.alarm["AlarmName"], account_id=self.account_id, region_name=self.region ) @@ -97,13 +95,13 @@ class CloudWatchStore(BaseStore): TAGS: TaggingService = CrossRegionAttribute(default=TaggingService) # maps resource ARN to alarms - alarms: Dict[str, LocalStackAlarm] = LocalAttribute(default=dict) + alarms: dict[str, LocalStackAlarm] = LocalAttribute(default=dict) # Contains all the Alarm Histories. Per documentation, an alarm history is retained even if the alarm is deleted, # making it necessary to save this at store level - histories: List[Dict] = LocalAttribute(default=list) + histories: list[dict] = LocalAttribute(default=list) - dashboards: Dict[str, LocalStackDashboard] = LocalAttribute(default=dict) + dashboards: dict[str, LocalStackDashboard] = LocalAttribute(default=dict) cloudwatch_stores = AccountRegionBundle("cloudwatch", CloudWatchStore) diff --git a/localstack-core/localstack/services/cloudwatch/provider.py b/localstack-core/localstack/services/cloudwatch/provider.py index 42e4b5fe94e58..f21e9e47485bc 100644 --- a/localstack-core/localstack/services/cloudwatch/provider.py +++ b/localstack-core/localstack/services/cloudwatch/provider.py @@ -1,7 +1,7 @@ import json import logging import uuid -from typing import Any, Optional +from typing import Any from xml.sax.saxutils import escape from moto.cloudwatch import cloudwatch_backends @@ -115,18 +115,18 @@ def put_metric_alarm( description: str, dimensions: list[dict[str, str]], alarm_actions: list[str], - metric_data_queries: Optional[list[Any]] = None, - datapoints_to_alarm: Optional[int] = None, - extended_statistic: Optional[str] = None, - ok_actions: Optional[list[str]] = None, - insufficient_data_actions: Optional[list[str]] = None, - unit: Optional[str] = None, + metric_data_queries: list[Any] | None = None, + datapoints_to_alarm: int | None = None, + extended_statistic: str | None = None, + ok_actions: list[str] | None = None, + insufficient_data_actions: list[str] | None = None, + unit: str | None = None, actions_enabled: bool = True, - treat_missing_data: Optional[str] = None, - evaluate_low_sample_count_percentile: Optional[str] = None, - threshold_metric_id: Optional[str] = None, - rule: Optional[str] = None, - tags: Optional[list[dict[str, str]]] = None, + treat_missing_data: str | None = None, + evaluate_low_sample_count_percentile: str | None = None, + threshold_metric_id: str | None = None, + rule: str | None = None, + tags: list[dict[str, str]] | None = None, ) -> FakeAlarm: if description: description = escape(description) diff --git a/localstack-core/localstack/services/cloudwatch/provider_v2.py b/localstack-core/localstack/services/cloudwatch/provider_v2.py index 31f737fec9e23..32fcde651a8fa 100644 --- a/localstack-core/localstack/services/cloudwatch/provider_v2.py +++ b/localstack-core/localstack/services/cloudwatch/provider_v2.py @@ -4,8 +4,6 @@ import re import threading import uuid -from datetime import timezone -from typing import List from localstack.aws.api import CommonServiceException, RequestContext, handler from localstack.aws.api.cloudwatch import ( @@ -244,7 +242,7 @@ def get_metric_data( label_options: LabelOptions = None, **kwargs, ) -> GetMetricDataOutput: - results: List[MetricDataResult] = [] + results: list[MetricDataResult] = [] limit = max_datapoints or 100_800 messages: MetricDataResultMessages = [] nxt = None @@ -345,7 +343,7 @@ def set_alarm_state( if old_state == state_value: return - alarm.alarm["StateTransitionedTimestamp"] = datetime.datetime.now(timezone.utc) + alarm.alarm["StateTransitionedTimestamp"] = datetime.datetime.now(datetime.UTC) # update startDate (=last ALARM date) - should only update when a new alarm is triggered # the date is only updated if we have a reason-data, which is set by an alarm if state_reason_data: diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py index 56aa3292de1f4..a9cb85b83b61e 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,57 +14,57 @@ class CloudWatchAlarmProperties(TypedDict): - ComparisonOperator: Optional[str] - EvaluationPeriods: Optional[int] - ActionsEnabled: Optional[bool] - AlarmActions: Optional[list[str]] - AlarmDescription: Optional[str] - AlarmName: Optional[str] - Arn: Optional[str] - DatapointsToAlarm: Optional[int] - Dimensions: Optional[list[Dimension]] - EvaluateLowSampleCountPercentile: Optional[str] - ExtendedStatistic: Optional[str] - Id: Optional[str] - InsufficientDataActions: Optional[list[str]] - MetricName: Optional[str] - Metrics: Optional[list[MetricDataQuery]] - Namespace: Optional[str] - OKActions: Optional[list[str]] - Period: Optional[int] - Statistic: Optional[str] - Threshold: Optional[float] - ThresholdMetricId: Optional[str] - TreatMissingData: Optional[str] - Unit: Optional[str] + ComparisonOperator: str | None + EvaluationPeriods: int | None + ActionsEnabled: bool | None + AlarmActions: list[str] | None + AlarmDescription: str | None + AlarmName: str | None + Arn: str | None + DatapointsToAlarm: int | None + Dimensions: list[Dimension] | None + EvaluateLowSampleCountPercentile: str | None + ExtendedStatistic: str | None + Id: str | None + InsufficientDataActions: list[str] | None + MetricName: str | None + Metrics: list[MetricDataQuery] | None + Namespace: str | None + OKActions: list[str] | None + Period: int | None + Statistic: str | None + Threshold: float | None + ThresholdMetricId: str | None + TreatMissingData: str | None + Unit: str | None class Dimension(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class Metric(TypedDict): - Dimensions: Optional[list[Dimension]] - MetricName: Optional[str] - Namespace: Optional[str] + Dimensions: list[Dimension] | None + MetricName: str | None + Namespace: str | None class MetricStat(TypedDict): - Metric: Optional[Metric] - Period: Optional[int] - Stat: Optional[str] - Unit: Optional[str] + Metric: Metric | None + Period: int | None + Stat: str | None + Unit: str | None class MetricDataQuery(TypedDict): - Id: Optional[str] - AccountId: Optional[str] - Expression: Optional[str] - Label: Optional[str] - MetricStat: Optional[MetricStat] - Period: Optional[int] - ReturnData: Optional[bool] + Id: str | None + AccountId: str | None + Expression: str | None + Label: str | None + MetricStat: MetricStat | None + Period: int | None + ReturnData: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py index 6dfffe39b52a4..13c82fed034ac 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_alarm_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudWatchAlarmProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::CloudWatch::Alarm" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudwatch.resource_providers.aws_cloudwatch_alarm import ( diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py index b6ca22b2e9f3f..cddbba144416f 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,17 +15,17 @@ class CloudWatchCompositeAlarmProperties(TypedDict): - AlarmRule: Optional[str] - ActionsEnabled: Optional[bool] - ActionsSuppressor: Optional[str] - ActionsSuppressorExtensionPeriod: Optional[int] - ActionsSuppressorWaitPeriod: Optional[int] - AlarmActions: Optional[list[str]] - AlarmDescription: Optional[str] - AlarmName: Optional[str] - Arn: Optional[str] - InsufficientDataActions: Optional[list[str]] - OKActions: Optional[list[str]] + AlarmRule: str | None + ActionsEnabled: bool | None + ActionsSuppressor: str | None + ActionsSuppressorExtensionPeriod: int | None + ActionsSuppressorWaitPeriod: int | None + AlarmActions: list[str] | None + AlarmDescription: str | None + AlarmName: str | None + Arn: str | None + InsufficientDataActions: list[str] | None + OKActions: list[str] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py index 867cebdbfe31d..9196997cea9e7 100644 --- a/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py +++ b/localstack-core/localstack/services/cloudwatch/resource_providers/aws_cloudwatch_compositealarm_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class CloudWatchCompositeAlarmProviderPlugin(CloudFormationResourceProviderPlugi name = "AWS::CloudWatch::CompositeAlarm" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.cloudwatch.resource_providers.aws_cloudwatch_compositealarm import ( diff --git a/localstack-core/localstack/services/dynamodb/packages.py b/localstack-core/localstack/services/dynamodb/packages.py index 9cbeafe6a489d..f80175b5df183 100644 --- a/localstack-core/localstack/services/dynamodb/packages.py +++ b/localstack-core/localstack/services/dynamodb/packages.py @@ -1,5 +1,4 @@ import os -from typing import List from localstack import config from localstack.constants import ARTIFACTS_REPO, MAVEN_REPO_URL @@ -28,7 +27,7 @@ def __init__(self): def _get_installer(self, _) -> PackageInstaller: return DynamoDBLocalPackageInstaller() - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["2"] diff --git a/localstack-core/localstack/services/dynamodb/provider.py b/localstack-core/localstack/services/dynamodb/provider.py index e59a2d8841502..a1492fc2b41d8 100644 --- a/localstack-core/localstack/services/dynamodb/provider.py +++ b/localstack-core/localstack/services/dynamodb/provider.py @@ -12,7 +12,6 @@ from contextlib import contextmanager from datetime import datetime from operator import itemgetter -from typing import Dict, List, Optional import requests import werkzeug @@ -492,7 +491,7 @@ class ExpiredItemsWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None self.mutex = threading.RLock() def start(self): @@ -796,7 +795,7 @@ def describe_table( store = get_store(context.account_id, context.region) # Update replication details - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) replica_description_list = [] @@ -865,7 +864,7 @@ def update_table( store = get_store(context.account_id, global_table_region) # Dict with source region to set of replicated regions - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) for replica_update in replica_updates: for key, details in replica_update.items(): @@ -1512,7 +1511,7 @@ def create_global_table( replication_group: ReplicaList, **kwargs, ) -> CreateGlobalTableOutput: - global_tables: Dict = get_store(context.account_id, context.region).GLOBAL_TABLES + global_tables: dict = get_store(context.account_id, context.region).GLOBAL_TABLES if global_table_name in global_tables: raise GlobalTableAlreadyExistsException("Global table with this name already exists") replication_group = [grp.copy() for grp in replication_group or []] @@ -1891,7 +1890,7 @@ def get_global_table_region(context: RequestContext, table_name: str) -> str: return context.region @staticmethod - def prepare_request_headers(headers: Dict, account_id: str, region_name: str): + def prepare_request_headers(headers: dict, account_id: str, region_name: str): """ Modify the Credentials field of Authorization header to achieve namespacing in DynamoDBLocal. """ @@ -1908,7 +1907,7 @@ def prepare_request_headers(headers: Dict, account_id: str, region_name: str): flags=re.IGNORECASE, ) - def fix_consumed_capacity(self, request: Dict, result: Dict): + def fix_consumed_capacity(self, request: dict, result: dict): # make sure we append 'ConsumedCapacity', which is properly # returned by dynalite, but not by AWS's DynamoDBLocal table_name = request.get("TableName") @@ -2282,7 +2281,7 @@ def get_updated_records( account_id: str, region_name: str, table_name: str, - existing_items: List, + existing_items: list, server_url: str, table_stream_type: TableStreamType, ) -> RecordsMap: diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py index af199a479576c..5fea595fd0605 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,125 +14,125 @@ class DynamoDBGlobalTableProperties(TypedDict): - AttributeDefinitions: Optional[list[AttributeDefinition]] - KeySchema: Optional[list[KeySchema]] - Replicas: Optional[list[ReplicaSpecification]] - Arn: Optional[str] - BillingMode: Optional[str] - GlobalSecondaryIndexes: Optional[list[GlobalSecondaryIndex]] - LocalSecondaryIndexes: Optional[list[LocalSecondaryIndex]] - SSESpecification: Optional[SSESpecification] - StreamArn: Optional[str] - StreamSpecification: Optional[StreamSpecification] - TableId: Optional[str] - TableName: Optional[str] - TimeToLiveSpecification: Optional[TimeToLiveSpecification] - WriteProvisionedThroughputSettings: Optional[WriteProvisionedThroughputSettings] + AttributeDefinitions: list[AttributeDefinition] | None + KeySchema: list[KeySchema] | None + Replicas: list[ReplicaSpecification] | None + Arn: str | None + BillingMode: str | None + GlobalSecondaryIndexes: list[GlobalSecondaryIndex] | None + LocalSecondaryIndexes: list[LocalSecondaryIndex] | None + SSESpecification: SSESpecification | None + StreamArn: str | None + StreamSpecification: StreamSpecification | None + TableId: str | None + TableName: str | None + TimeToLiveSpecification: TimeToLiveSpecification | None + WriteProvisionedThroughputSettings: WriteProvisionedThroughputSettings | None class AttributeDefinition(TypedDict): - AttributeName: Optional[str] - AttributeType: Optional[str] + AttributeName: str | None + AttributeType: str | None class KeySchema(TypedDict): - AttributeName: Optional[str] - KeyType: Optional[str] + AttributeName: str | None + KeyType: str | None class Projection(TypedDict): - NonKeyAttributes: Optional[list[str]] - ProjectionType: Optional[str] + NonKeyAttributes: list[str] | None + ProjectionType: str | None class TargetTrackingScalingPolicyConfiguration(TypedDict): - TargetValue: Optional[float] - DisableScaleIn: Optional[bool] - ScaleInCooldown: Optional[int] - ScaleOutCooldown: Optional[int] + TargetValue: float | None + DisableScaleIn: bool | None + ScaleInCooldown: int | None + ScaleOutCooldown: int | None class CapacityAutoScalingSettings(TypedDict): - MaxCapacity: Optional[int] - MinCapacity: Optional[int] - TargetTrackingScalingPolicyConfiguration: Optional[TargetTrackingScalingPolicyConfiguration] - SeedCapacity: Optional[int] + MaxCapacity: int | None + MinCapacity: int | None + TargetTrackingScalingPolicyConfiguration: TargetTrackingScalingPolicyConfiguration | None + SeedCapacity: int | None class WriteProvisionedThroughputSettings(TypedDict): - WriteCapacityAutoScalingSettings: Optional[CapacityAutoScalingSettings] + WriteCapacityAutoScalingSettings: CapacityAutoScalingSettings | None class GlobalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] - WriteProvisionedThroughputSettings: Optional[WriteProvisionedThroughputSettings] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None + WriteProvisionedThroughputSettings: WriteProvisionedThroughputSettings | None class LocalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None class ContributorInsightsSpecification(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class ReadProvisionedThroughputSettings(TypedDict): - ReadCapacityAutoScalingSettings: Optional[CapacityAutoScalingSettings] - ReadCapacityUnits: Optional[int] + ReadCapacityAutoScalingSettings: CapacityAutoScalingSettings | None + ReadCapacityUnits: int | None class ReplicaGlobalSecondaryIndexSpecification(TypedDict): - IndexName: Optional[str] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - ReadProvisionedThroughputSettings: Optional[ReadProvisionedThroughputSettings] + IndexName: str | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + ReadProvisionedThroughputSettings: ReadProvisionedThroughputSettings | None class PointInTimeRecoverySpecification(TypedDict): - PointInTimeRecoveryEnabled: Optional[bool] + PointInTimeRecoveryEnabled: bool | None class ReplicaSSESpecification(TypedDict): - KMSMasterKeyId: Optional[str] + KMSMasterKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class KinesisStreamSpecification(TypedDict): - StreamArn: Optional[str] + StreamArn: str | None class ReplicaSpecification(TypedDict): - Region: Optional[str] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - DeletionProtectionEnabled: Optional[bool] - GlobalSecondaryIndexes: Optional[list[ReplicaGlobalSecondaryIndexSpecification]] - KinesisStreamSpecification: Optional[KinesisStreamSpecification] - PointInTimeRecoverySpecification: Optional[PointInTimeRecoverySpecification] - ReadProvisionedThroughputSettings: Optional[ReadProvisionedThroughputSettings] - SSESpecification: Optional[ReplicaSSESpecification] - TableClass: Optional[str] - Tags: Optional[list[Tag]] + Region: str | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + DeletionProtectionEnabled: bool | None + GlobalSecondaryIndexes: list[ReplicaGlobalSecondaryIndexSpecification] | None + KinesisStreamSpecification: KinesisStreamSpecification | None + PointInTimeRecoverySpecification: PointInTimeRecoverySpecification | None + ReadProvisionedThroughputSettings: ReadProvisionedThroughputSettings | None + SSESpecification: ReplicaSSESpecification | None + TableClass: str | None + Tags: list[Tag] | None class SSESpecification(TypedDict): - SSEEnabled: Optional[bool] - SSEType: Optional[str] + SSEEnabled: bool | None + SSEType: str | None class StreamSpecification(TypedDict): - StreamViewType: Optional[str] + StreamViewType: str | None class TimeToLiveSpecification(TypedDict): - Enabled: Optional[bool] - AttributeName: Optional[str] + Enabled: bool | None + AttributeName: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py index 8de0265d3d5f1..ca7cb4448f530 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_globaltable_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class DynamoDBGlobalTableProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::DynamoDB::GlobalTable" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.dynamodb.resource_providers.aws_dynamodb_globaltable import ( diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py index 469c944cca898..1c27ee05eebae 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,113 +14,113 @@ class DynamoDBTableProperties(TypedDict): - KeySchema: Optional[list[KeySchema] | dict] - Arn: Optional[str] - AttributeDefinitions: Optional[list[AttributeDefinition]] - BillingMode: Optional[str] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - DeletionProtectionEnabled: Optional[bool] - GlobalSecondaryIndexes: Optional[list[GlobalSecondaryIndex]] - ImportSourceSpecification: Optional[ImportSourceSpecification] - KinesisStreamSpecification: Optional[KinesisStreamSpecification] - LocalSecondaryIndexes: Optional[list[LocalSecondaryIndex]] - PointInTimeRecoverySpecification: Optional[PointInTimeRecoverySpecification] - ProvisionedThroughput: Optional[ProvisionedThroughput] - SSESpecification: Optional[SSESpecification] - StreamArn: Optional[str] - StreamSpecification: Optional[StreamSpecification] - TableClass: Optional[str] - TableName: Optional[str] - Tags: Optional[list[Tag]] - TimeToLiveSpecification: Optional[TimeToLiveSpecification] + KeySchema: list[KeySchema] | dict | None + Arn: str | None + AttributeDefinitions: list[AttributeDefinition] | None + BillingMode: str | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + DeletionProtectionEnabled: bool | None + GlobalSecondaryIndexes: list[GlobalSecondaryIndex] | None + ImportSourceSpecification: ImportSourceSpecification | None + KinesisStreamSpecification: KinesisStreamSpecification | None + LocalSecondaryIndexes: list[LocalSecondaryIndex] | None + PointInTimeRecoverySpecification: PointInTimeRecoverySpecification | None + ProvisionedThroughput: ProvisionedThroughput | None + SSESpecification: SSESpecification | None + StreamArn: str | None + StreamSpecification: StreamSpecification | None + TableClass: str | None + TableName: str | None + Tags: list[Tag] | None + TimeToLiveSpecification: TimeToLiveSpecification | None class AttributeDefinition(TypedDict): - AttributeName: Optional[str] - AttributeType: Optional[str] + AttributeName: str | None + AttributeType: str | None class KeySchema(TypedDict): - AttributeName: Optional[str] - KeyType: Optional[str] + AttributeName: str | None + KeyType: str | None class Projection(TypedDict): - NonKeyAttributes: Optional[list[str]] - ProjectionType: Optional[str] + NonKeyAttributes: list[str] | None + ProjectionType: str | None class ProvisionedThroughput(TypedDict): - ReadCapacityUnits: Optional[int] - WriteCapacityUnits: Optional[int] + ReadCapacityUnits: int | None + WriteCapacityUnits: int | None class ContributorInsightsSpecification(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class GlobalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] - ContributorInsightsSpecification: Optional[ContributorInsightsSpecification] - ProvisionedThroughput: Optional[ProvisionedThroughput] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None + ContributorInsightsSpecification: ContributorInsightsSpecification | None + ProvisionedThroughput: ProvisionedThroughput | None class LocalSecondaryIndex(TypedDict): - IndexName: Optional[str] - KeySchema: Optional[list[KeySchema]] - Projection: Optional[Projection] + IndexName: str | None + KeySchema: list[KeySchema] | None + Projection: Projection | None class PointInTimeRecoverySpecification(TypedDict): - PointInTimeRecoveryEnabled: Optional[bool] + PointInTimeRecoveryEnabled: bool | None class SSESpecification(TypedDict): - SSEEnabled: Optional[bool] - KMSMasterKeyId: Optional[str] - SSEType: Optional[str] + SSEEnabled: bool | None + KMSMasterKeyId: str | None + SSEType: str | None class StreamSpecification(TypedDict): - StreamViewType: Optional[str] + StreamViewType: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class TimeToLiveSpecification(TypedDict): - AttributeName: Optional[str] - Enabled: Optional[bool] + AttributeName: str | None + Enabled: bool | None class KinesisStreamSpecification(TypedDict): - StreamArn: Optional[str] + StreamArn: str | None class S3BucketSource(TypedDict): - S3Bucket: Optional[str] - S3BucketOwner: Optional[str] - S3KeyPrefix: Optional[str] + S3Bucket: str | None + S3BucketOwner: str | None + S3KeyPrefix: str | None class Csv(TypedDict): - Delimiter: Optional[str] - HeaderList: Optional[list[str]] + Delimiter: str | None + HeaderList: list[str] | None class InputFormatOptions(TypedDict): - Csv: Optional[Csv] + Csv: Csv | None class ImportSourceSpecification(TypedDict): - InputFormat: Optional[str] - S3BucketSource: Optional[S3BucketSource] - InputCompressionType: Optional[str] - InputFormatOptions: Optional[InputFormatOptions] + InputFormat: str | None + S3BucketSource: S3BucketSource | None + InputCompressionType: str | None + InputFormatOptions: InputFormatOptions | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py index 5f263b9e9d068..a2fba6f7af626 100644 --- a/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py +++ b/localstack-core/localstack/services/dynamodb/resource_providers/aws_dynamodb_table_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class DynamoDBTableProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::DynamoDB::Table" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.dynamodb.resource_providers.aws_dynamodb_table import ( diff --git a/localstack-core/localstack/services/dynamodb/utils.py b/localstack-core/localstack/services/dynamodb/utils.py index 4ff065440abec..530c937d0424e 100644 --- a/localstack-core/localstack/services/dynamodb/utils.py +++ b/localstack-core/localstack/services/dynamodb/utils.py @@ -1,7 +1,6 @@ import logging import re from binascii import crc32 -from typing import Dict, List, Optional from boto3.dynamodb.types import TypeDeserializer, TypeSerializer from cachetools import TTLCache @@ -61,7 +60,7 @@ def get_ddb_access_key(account_id: str, region_name: str) -> str: class ItemSet: """Represents a set of items and provides utils to find individual items in the set""" - def __init__(self, items: List[Dict], key_schema: List[Dict]): + def __init__(self, items: list[dict], key_schema: list[dict]): self.items_list = items self.key_schema = key_schema self._build_dict() @@ -71,11 +70,11 @@ def _build_dict(self): for item in self.items_list: self.items_dict[self._hashable_key(item)] = item - def _hashable_key(self, item: Dict): + def _hashable_key(self, item: dict): keys = SchemaExtractor.extract_keys_for_schema(item=item, key_schema=self.key_schema) return canonical_json(keys) - def find_item(self, item: Dict) -> Optional[Dict]: + def find_item(self, item: dict) -> dict | None: key = self._hashable_key(item) return self.items_dict.get(key) @@ -83,13 +82,13 @@ def find_item(self, item: Dict) -> Optional[Dict]: class SchemaExtractor: @classmethod def extract_keys( - cls, item: Dict, table_name: str, account_id: str, region_name: str - ) -> Optional[Dict]: + cls, item: dict, table_name: str, account_id: str, region_name: str + ) -> dict | None: key_schema = cls.get_key_schema(table_name, account_id, region_name) return cls.extract_keys_for_schema(item, key_schema) @classmethod - def extract_keys_for_schema(cls, item: Dict, key_schema: List[Dict]): + def extract_keys_for_schema(cls, item: dict, key_schema: list[dict]): result = {} for key in key_schema: attr_name = key["AttributeName"] @@ -104,10 +103,10 @@ def extract_keys_for_schema(cls, item: Dict, key_schema: List[Dict]): @classmethod def get_key_schema( cls, table_name: str, account_id: str, region_name: str - ) -> Optional[List[Dict]]: + ) -> list[dict] | None: from localstack.services.dynamodb.provider import get_store - table_definitions: Dict = get_store( + table_definitions: dict = get_store( account_id=account_id, region_name=region_name, ).table_definitions @@ -169,12 +168,12 @@ def get_ddb_local_client(account_id: str, region_name: str, endpoint_url: str): @staticmethod def find_existing_item( - put_item: Dict, + put_item: dict, table_name: str, account_id: str, region_name: str, endpoint_url: str, - ) -> Optional[AttributeMap]: + ) -> AttributeMap | None: from localstack.services.dynamodb.provider import ValidationException ddb_client = ItemFinder.get_ddb_local_client(account_id, region_name, endpoint_url) @@ -273,7 +272,7 @@ def find_existing_items( @classmethod def list_existing_items_for_statement( cls, partiql_statement: str, account_id: str, region_name: str, endpoint_url: str - ) -> List: + ) -> list: table_name = extract_table_name_from_partiql_update(partiql_statement) if not table_name: return [] @@ -288,7 +287,7 @@ def list_existing_items_for_statement( @staticmethod def get_all_table_items( account_id: str, region_name: str, table_name: str, endpoint_url: str - ) -> List: + ) -> list: ddb_client = ItemFinder.get_ddb_local_client(account_id, region_name, endpoint_url) dynamodb_kwargs = {"TableName": table_name} all_items = list_all_resources( @@ -300,7 +299,7 @@ def get_all_table_items( return all_items -def extract_table_name_from_partiql_update(statement: str) -> Optional[str]: +def extract_table_name_from_partiql_update(statement: str) -> str | None: regex = r"^\s*(UPDATE|INSERT\s+INTO|DELETE\s+FROM)\s+([^\s]+).*" match = re.match(regex, statement, flags=re.IGNORECASE | re.MULTILINE) return match and match.group(2) diff --git a/localstack-core/localstack/services/dynamodb/v2/provider.py b/localstack-core/localstack/services/dynamodb/v2/provider.py index 17921f59c4e2e..b7f22b83b8a59 100644 --- a/localstack-core/localstack/services/dynamodb/v2/provider.py +++ b/localstack-core/localstack/services/dynamodb/v2/provider.py @@ -9,7 +9,6 @@ from contextlib import contextmanager from datetime import datetime from operator import itemgetter -from typing import Dict, Optional import requests import werkzeug @@ -340,7 +339,7 @@ class ExpiredItemsWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None self.mutex = threading.RLock() def start(self): @@ -607,7 +606,7 @@ def describe_table( store = get_store(context.account_id, context.region) # Update replication details - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) replica_description_list = [] @@ -676,7 +675,7 @@ def update_table( store = get_store(context.account_id, global_table_region) # Dict with source region to set of replicated regions - replicas: Dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) + replicas: dict[RegionName, ReplicaDescription] = store.REPLICAS.get(table_name, {}) for replica_update in replica_updates: for key, details in replica_update.items(): @@ -1036,7 +1035,7 @@ def create_global_table( replication_group: ReplicaList, **kwargs, ) -> CreateGlobalTableOutput: - global_tables: Dict = get_store(context.account_id, context.region).GLOBAL_TABLES + global_tables: dict = get_store(context.account_id, context.region).GLOBAL_TABLES if global_table_name in global_tables: raise GlobalTableAlreadyExistsException("Global table with this name already exists") replication_group = [grp.copy() for grp in replication_group or []] @@ -1410,7 +1409,7 @@ def get_global_table_region(context: RequestContext, table_name: str) -> str: return context.region @staticmethod - def prepare_request_headers(headers: Dict, account_id: str, region_name: str): + def prepare_request_headers(headers: dict, account_id: str, region_name: str): """ Modify the Credentials field of Authorization header to achieve namespacing in DynamoDBLocal. """ @@ -1427,7 +1426,7 @@ def prepare_request_headers(headers: Dict, account_id: str, region_name: str): flags=re.IGNORECASE, ) - def fix_consumed_capacity(self, request: Dict, result: Dict): + def fix_consumed_capacity(self, request: dict, result: dict): # make sure we append 'ConsumedCapacity', which is properly # returned by dynalite, but not by AWS's DynamoDBLocal table_name = request.get("TableName") diff --git a/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py b/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py index e9164465fdd57..45ae3f3bc472f 100644 --- a/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py +++ b/localstack-core/localstack/services/dynamodbstreams/dynamodbstreams_api.py @@ -1,6 +1,6 @@ import logging import threading -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING from bson.json_util import dumps @@ -206,7 +206,7 @@ def kinesis_shard_id(dynamodbstream_shard_id: str) -> str: return f"{shard_params[0]}-{shard_params[-1]}" -def get_shard_id(stream: Dict, kinesis_shard_id: str) -> str: +def get_shard_id(stream: dict, kinesis_shard_id: str) -> str: ddb_stream_shard_id = stream.get("shards_id_map", {}).get(kinesis_shard_id) if not ddb_stream_shard_id: ddb_stream_shard_id = shard_id(kinesis_shard_id) diff --git a/localstack-core/localstack/services/dynamodbstreams/models.py b/localstack-core/localstack/services/dynamodbstreams/models.py index a8a6672babf11..858954f3f9772 100644 --- a/localstack-core/localstack/services/dynamodbstreams/models.py +++ b/localstack-core/localstack/services/dynamodbstreams/models.py @@ -1,11 +1,9 @@ -from typing import Dict - from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute class DynamoDbStreamsStore(BaseStore): # maps table names to DynamoDB stream descriptions - ddb_streams: Dict[str, dict] = LocalAttribute(default=dict) + ddb_streams: dict[str, dict] = LocalAttribute(default=dict) dynamodbstreams_stores = AccountRegionBundle("dynamodbstreams", DynamoDbStreamsStore) diff --git a/localstack-core/localstack/services/ec2/patches.py b/localstack-core/localstack/services/ec2/patches.py index d2037015905ef..66ead222acfa6 100644 --- a/localstack-core/localstack/services/ec2/patches.py +++ b/localstack-core/localstack/services/ec2/patches.py @@ -1,7 +1,7 @@ import logging -from typing import Optional from moto.ec2 import models as ec2_models +from moto.ec2.models.vpcs import VPCEndPoint from moto.utilities.id_generator import Tags from localstack.services.ec2.exceptions import ( @@ -15,6 +15,8 @@ localstack_id, ) from localstack.utils.patch import patch +from localstack.utils.strings import short_uid +from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) @@ -93,12 +95,31 @@ def generate(self, existing_ids: ExistingIds = None, tags: Tags = None) -> str: def apply_patches(): + @patch(ec2_models.vpcs.VPCBackend.create_vpc_endpoint) + def ec2_create_vpc_endpoint( + fn: ec2_models.VPCBackend.create_vpc_endpoint, self: ec2_models.VPCBackend, **kwargs + ) -> VPCEndPoint: + vpc_endpoint: VPCEndPoint = fn(self=self, **kwargs) + + # moto returns the dns entries as `.com.amazonaws..` + # to keep the aws style `...vpce.amazonaws.com` and ensure it can be routed + # by the individual services in LocalStack we will be creating the following entries: + # `...vpce.` + if service_name := kwargs.get("service_name"): + ls_service_name = ".".join(service_name.split(".")[::-1]).replace( + "amazonaws.com", f"vpce.{localstack_host()}" + ) + for dns_entry in vpc_endpoint.dns_entries or []: + dns_entry["dns_name"] = f"{vpc_endpoint.id}-{short_uid()}.{ls_service_name}" + + return vpc_endpoint + @patch(ec2_models.subnets.SubnetBackend.create_subnet) def ec2_create_subnet( fn: ec2_models.subnets.SubnetBackend.create_subnet, self: ec2_models.subnets.SubnetBackend, *args, - tags: Optional[dict[str, str]] = None, + tags: dict[str, str] | None = None, **kwargs, ): # Patch this method so that we can create a subnet with a specific "custom" @@ -151,8 +172,8 @@ def ec2_create_security_group( self: ec2_models.security_groups.SecurityGroupBackend, name: str, *args, - vpc_id: Optional[str] = None, - tags: Optional[dict[str, str]] = None, + vpc_id: str | None = None, + tags: dict[str, str] | None = None, force: bool = False, **kwargs, ): @@ -191,7 +212,7 @@ def ec2_create_vpc( self: ec2_models.vpcs.VPCBackend, cidr_block: str, *args, - tags: Optional[list[dict[str, str]]] = None, + tags: list[dict[str, str]] | None = None, is_default: bool = False, **kwargs, ): diff --git a/localstack-core/localstack/services/ec2/provider.py b/localstack-core/localstack/services/ec2/provider.py index ab52195e4cfa8..03a72e873b35f 100644 --- a/localstack-core/localstack/services/ec2/provider.py +++ b/localstack-core/localstack/services/ec2/provider.py @@ -3,7 +3,7 @@ import logging import re from abc import ABC -from datetime import datetime, timezone +from datetime import UTC, datetime from botocore.parsers import ResponseParserError from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase @@ -173,13 +173,13 @@ def describe_reserved_instances( ReservedInstances( AvailabilityZone="eu-central-1a", Duration=2628000, - End=datetime(2016, 6, 30, tzinfo=timezone.utc), + End=datetime(2016, 6, 30, tzinfo=UTC), FixedPrice=0.0, InstanceCount=2, InstanceType=InstanceType.t2_small, ProductDescription=RIProductDescription.Linux_UNIX, ReservedInstancesId=long_uid(), - Start=datetime(2016, 1, 1, tzinfo=timezone.utc), + Start=datetime(2016, 1, 1, tzinfo=UTC), State=ReservedInstanceState.active, UsagePrice=0.05, CurrencyCode=CurrencyCodeValues.USD, diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py index 03665a7c45fb6..c89efbef56a00 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class EC2DHCPOptionsProperties(TypedDict): - DhcpOptionsId: Optional[str] - DomainName: Optional[str] - DomainNameServers: Optional[list[str]] - NetbiosNameServers: Optional[list[str]] - NetbiosNodeType: Optional[int] - NtpServers: Optional[list[str]] - Tags: Optional[list[Tag]] + DhcpOptionsId: str | None + DomainName: str | None + DomainNameServers: list[str] | None + NetbiosNameServers: list[str] | None + NetbiosNodeType: int | None + NtpServers: list[str] | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py index c3ac8bb5a5827..947069d5b29d7 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_dhcpoptions_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2DHCPOptionsProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::DHCPOptions" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_dhcpoptions import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py index 8c33cde7b2ab8..457dd54450f27 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance.py @@ -3,7 +3,7 @@ import base64 from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,155 +16,155 @@ class EC2InstanceProperties(TypedDict): - AdditionalInfo: Optional[str] - Affinity: Optional[str] - AvailabilityZone: Optional[str] - BlockDeviceMappings: Optional[list[BlockDeviceMapping]] - CpuOptions: Optional[CpuOptions] - CreditSpecification: Optional[CreditSpecification] - DisableApiTermination: Optional[bool] - EbsOptimized: Optional[bool] - ElasticGpuSpecifications: Optional[list[ElasticGpuSpecification]] - ElasticInferenceAccelerators: Optional[list[ElasticInferenceAccelerator]] - EnclaveOptions: Optional[EnclaveOptions] - HibernationOptions: Optional[HibernationOptions] - HostId: Optional[str] - HostResourceGroupArn: Optional[str] - IamInstanceProfile: Optional[str] - Id: Optional[str] - ImageId: Optional[str] - InstanceInitiatedShutdownBehavior: Optional[str] - InstanceType: Optional[str] - Ipv6AddressCount: Optional[int] - Ipv6Addresses: Optional[list[InstanceIpv6Address]] - KernelId: Optional[str] - KeyName: Optional[str] - LaunchTemplate: Optional[LaunchTemplateSpecification] - LicenseSpecifications: Optional[list[LicenseSpecification]] - Monitoring: Optional[bool] - NetworkInterfaces: Optional[list[NetworkInterface]] - PlacementGroupName: Optional[str] - PrivateDnsName: Optional[str] - PrivateDnsNameOptions: Optional[PrivateDnsNameOptions] - PrivateIp: Optional[str] - PrivateIpAddress: Optional[str] - PropagateTagsToVolumeOnCreation: Optional[bool] - PublicDnsName: Optional[str] - PublicIp: Optional[str] - RamdiskId: Optional[str] - SecurityGroupIds: Optional[list[str]] - SecurityGroups: Optional[list[str]] - SourceDestCheck: Optional[bool] - SsmAssociations: Optional[list[SsmAssociation]] - SubnetId: Optional[str] - Tags: Optional[list[Tag]] - Tenancy: Optional[str] - UserData: Optional[str] - Volumes: Optional[list[Volume]] + AdditionalInfo: str | None + Affinity: str | None + AvailabilityZone: str | None + BlockDeviceMappings: list[BlockDeviceMapping] | None + CpuOptions: CpuOptions | None + CreditSpecification: CreditSpecification | None + DisableApiTermination: bool | None + EbsOptimized: bool | None + ElasticGpuSpecifications: list[ElasticGpuSpecification] | None + ElasticInferenceAccelerators: list[ElasticInferenceAccelerator] | None + EnclaveOptions: EnclaveOptions | None + HibernationOptions: HibernationOptions | None + HostId: str | None + HostResourceGroupArn: str | None + IamInstanceProfile: str | None + Id: str | None + ImageId: str | None + InstanceInitiatedShutdownBehavior: str | None + InstanceType: str | None + Ipv6AddressCount: int | None + Ipv6Addresses: list[InstanceIpv6Address] | None + KernelId: str | None + KeyName: str | None + LaunchTemplate: LaunchTemplateSpecification | None + LicenseSpecifications: list[LicenseSpecification] | None + Monitoring: bool | None + NetworkInterfaces: list[NetworkInterface] | None + PlacementGroupName: str | None + PrivateDnsName: str | None + PrivateDnsNameOptions: PrivateDnsNameOptions | None + PrivateIp: str | None + PrivateIpAddress: str | None + PropagateTagsToVolumeOnCreation: bool | None + PublicDnsName: str | None + PublicIp: str | None + RamdiskId: str | None + SecurityGroupIds: list[str] | None + SecurityGroups: list[str] | None + SourceDestCheck: bool | None + SsmAssociations: list[SsmAssociation] | None + SubnetId: str | None + Tags: list[Tag] | None + Tenancy: str | None + UserData: str | None + Volumes: list[Volume] | None class Ebs(TypedDict): - DeleteOnTermination: Optional[bool] - Encrypted: Optional[bool] - Iops: Optional[int] - KmsKeyId: Optional[str] - SnapshotId: Optional[str] - VolumeSize: Optional[int] - VolumeType: Optional[str] + DeleteOnTermination: bool | None + Encrypted: bool | None + Iops: int | None + KmsKeyId: str | None + SnapshotId: str | None + VolumeSize: int | None + VolumeType: str | None class BlockDeviceMapping(TypedDict): - DeviceName: Optional[str] - Ebs: Optional[Ebs] - NoDevice: Optional[dict] - VirtualName: Optional[str] + DeviceName: str | None + Ebs: Ebs | None + NoDevice: dict | None + VirtualName: str | None class InstanceIpv6Address(TypedDict): - Ipv6Address: Optional[str] + Ipv6Address: str | None class ElasticGpuSpecification(TypedDict): - Type: Optional[str] + Type: str | None class ElasticInferenceAccelerator(TypedDict): - Type: Optional[str] - Count: Optional[int] + Type: str | None + Count: int | None class Volume(TypedDict): - Device: Optional[str] - VolumeId: Optional[str] + Device: str | None + VolumeId: str | None class LaunchTemplateSpecification(TypedDict): - Version: Optional[str] - LaunchTemplateId: Optional[str] - LaunchTemplateName: Optional[str] + Version: str | None + LaunchTemplateId: str | None + LaunchTemplateName: str | None class EnclaveOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class PrivateIpAddressSpecification(TypedDict): - Primary: Optional[bool] - PrivateIpAddress: Optional[str] + Primary: bool | None + PrivateIpAddress: str | None class NetworkInterface(TypedDict): - DeviceIndex: Optional[str] - AssociateCarrierIpAddress: Optional[bool] - AssociatePublicIpAddress: Optional[bool] - DeleteOnTermination: Optional[bool] - Description: Optional[str] - GroupSet: Optional[list[str]] - Ipv6AddressCount: Optional[int] - Ipv6Addresses: Optional[list[InstanceIpv6Address]] - NetworkInterfaceId: Optional[str] - PrivateIpAddress: Optional[str] - PrivateIpAddresses: Optional[list[PrivateIpAddressSpecification]] - SecondaryPrivateIpAddressCount: Optional[int] - SubnetId: Optional[str] + DeviceIndex: str | None + AssociateCarrierIpAddress: bool | None + AssociatePublicIpAddress: bool | None + DeleteOnTermination: bool | None + Description: str | None + GroupSet: list[str] | None + Ipv6AddressCount: int | None + Ipv6Addresses: list[InstanceIpv6Address] | None + NetworkInterfaceId: str | None + PrivateIpAddress: str | None + PrivateIpAddresses: list[PrivateIpAddressSpecification] | None + SecondaryPrivateIpAddressCount: int | None + SubnetId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class HibernationOptions(TypedDict): - Configured: Optional[bool] + Configured: bool | None class LicenseSpecification(TypedDict): - LicenseConfigurationArn: Optional[str] + LicenseConfigurationArn: str | None class CpuOptions(TypedDict): - CoreCount: Optional[int] - ThreadsPerCore: Optional[int] + CoreCount: int | None + ThreadsPerCore: int | None class PrivateDnsNameOptions(TypedDict): - EnableResourceNameDnsAAAARecord: Optional[bool] - EnableResourceNameDnsARecord: Optional[bool] - HostnameType: Optional[str] + EnableResourceNameDnsAAAARecord: bool | None + EnableResourceNameDnsARecord: bool | None + HostnameType: str | None class AssociationParameter(TypedDict): - Key: Optional[str] - Value: Optional[list[str]] + Key: str | None + Value: list[str] | None class SsmAssociation(TypedDict): - DocumentName: Optional[str] - AssociationParameters: Optional[list[AssociationParameter]] + DocumentName: str | None + AssociationParameters: list[AssociationParameter] | None class CreditSpecification(TypedDict): - CPUCredits: Optional[str] + CPUCredits: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py index 60f400297a47f..8a218e994d068 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_instance_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2InstanceProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::Instance" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_instance import EC2InstanceProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py index 1ad0d6981b9c0..075f8abe7bfa6 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,13 +14,13 @@ class EC2InternetGatewayProperties(TypedDict): - InternetGatewayId: Optional[str] - Tags: Optional[list[Tag]] + InternetGatewayId: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py index 51c889fae01a0..37df146193039 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_internetgateway_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2InternetGatewayProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::InternetGateway" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_internetgateway import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py index 8c03d6bc738b5..34eeb3d6e638d 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class EC2KeyPairProperties(TypedDict): - KeyName: Optional[str] - KeyFingerprint: Optional[str] - KeyFormat: Optional[str] - KeyPairId: Optional[str] - KeyType: Optional[str] - PublicKeyMaterial: Optional[str] - Tags: Optional[list[Tag]] + KeyName: str | None + KeyFingerprint: str | None + KeyFormat: str | None + KeyPairId: str | None + KeyType: str | None + PublicKeyMaterial: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py index 5bb9524b1f667..655c7d6d7e204 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_keypair_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2KeyPairProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::KeyPair" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_keypair import EC2KeyPairProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py index de03079d89699..c29b695adc4d8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,21 +14,21 @@ class EC2NatGatewayProperties(TypedDict): - SubnetId: Optional[str] - AllocationId: Optional[str] - ConnectivityType: Optional[str] - MaxDrainDurationSeconds: Optional[int] - NatGatewayId: Optional[str] - PrivateIpAddress: Optional[str] - SecondaryAllocationIds: Optional[list[str]] - SecondaryPrivateIpAddressCount: Optional[int] - SecondaryPrivateIpAddresses: Optional[list[str]] - Tags: Optional[list[Tag]] + SubnetId: str | None + AllocationId: str | None + ConnectivityType: str | None + MaxDrainDurationSeconds: int | None + NatGatewayId: str | None + PrivateIpAddress: str | None + SecondaryAllocationIds: list[str] | None + SecondaryPrivateIpAddressCount: int | None + SecondaryPrivateIpAddresses: list[str] | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py index e8036702f5e79..9fe089b969d68 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_natgateway_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2NatGatewayProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::NatGateway" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_natgateway import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py index 47d36951d0068..f4f2c07634cd8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class EC2NetworkAclProperties(TypedDict): - VpcId: Optional[str] - Id: Optional[str] - Tags: Optional[list[Tag]] + VpcId: str | None + Id: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py index 0f24a9cd40adc..026799db63eee 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_networkacl_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2NetworkAclProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::NetworkAcl" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_networkacl import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py index 8308fb5bfa990..298e41b209219 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,25 +14,25 @@ class EC2PrefixListProperties(TypedDict): - AddressFamily: Optional[str] - MaxEntries: Optional[int] - PrefixListName: Optional[str] - Arn: Optional[str] - Entries: Optional[list[Entry]] - OwnerId: Optional[str] - PrefixListId: Optional[str] - Tags: Optional[list[Tag]] - Version: Optional[int] + AddressFamily: str | None + MaxEntries: int | None + PrefixListName: str | None + Arn: str | None + Entries: list[Entry] | None + OwnerId: str | None + PrefixListId: str | None + Tags: list[Tag] | None + Version: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class Entry(TypedDict): - Cidr: Optional[str] - Description: Optional[str] + Cidr: str | None + Description: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py index 5d8b993d28409..5aadb404eda7c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_prefixlist_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2PrefixListProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::PrefixList" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_prefixlist import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py index c779541d04229..6665b4b1594b0 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from moto.ec2.utils import generate_route_id @@ -16,20 +16,20 @@ class EC2RouteProperties(TypedDict): - RouteTableId: Optional[str] - CarrierGatewayId: Optional[str] - DestinationCidrBlock: Optional[str] - DestinationIpv6CidrBlock: Optional[str] - EgressOnlyInternetGatewayId: Optional[str] - GatewayId: Optional[str] - Id: Optional[str] - InstanceId: Optional[str] - LocalGatewayId: Optional[str] - NatGatewayId: Optional[str] - NetworkInterfaceId: Optional[str] - TransitGatewayId: Optional[str] - VpcEndpointId: Optional[str] - VpcPeeringConnectionId: Optional[str] + RouteTableId: str | None + CarrierGatewayId: str | None + DestinationCidrBlock: str | None + DestinationIpv6CidrBlock: str | None + EgressOnlyInternetGatewayId: str | None + GatewayId: str | None + Id: str | None + InstanceId: str | None + LocalGatewayId: str | None + NatGatewayId: str | None + NetworkInterfaceId: str | None + TransitGatewayId: str | None + VpcEndpointId: str | None + VpcPeeringConnectionId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py index abd759b08aaca..ae127a0a939a7 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_route_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2RouteProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::Route" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_route import EC2RouteProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py index 618c3fad99c08..6769004c93454 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class EC2RouteTableProperties(TypedDict): - VpcId: Optional[str] - RouteTableId: Optional[str] - Tags: Optional[list[Tag]] + VpcId: str | None + RouteTableId: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py index 07396c832bf66..2184aadb1a0b5 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_routetable_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2RouteTableProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::RouteTable" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_routetable import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py index 39621b8e5178e..77fd0e60f2ca1 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,43 +15,43 @@ class EC2SecurityGroupProperties(TypedDict): - GroupDescription: Optional[str] - GroupId: Optional[str] - GroupName: Optional[str] - Id: Optional[str] - SecurityGroupEgress: Optional[list[Egress]] - SecurityGroupIngress: Optional[list[Ingress]] - Tags: Optional[list[Tag]] - VpcId: Optional[str] + GroupDescription: str | None + GroupId: str | None + GroupName: str | None + Id: str | None + SecurityGroupEgress: list[Egress] | None + SecurityGroupIngress: list[Ingress] | None + Tags: list[Tag] | None + VpcId: str | None class Ingress(TypedDict): - IpProtocol: Optional[str] - CidrIp: Optional[str] - CidrIpv6: Optional[str] - Description: Optional[str] - FromPort: Optional[int] - SourcePrefixListId: Optional[str] - SourceSecurityGroupId: Optional[str] - SourceSecurityGroupName: Optional[str] - SourceSecurityGroupOwnerId: Optional[str] - ToPort: Optional[int] + IpProtocol: str | None + CidrIp: str | None + CidrIpv6: str | None + Description: str | None + FromPort: int | None + SourcePrefixListId: str | None + SourceSecurityGroupId: str | None + SourceSecurityGroupName: str | None + SourceSecurityGroupOwnerId: str | None + ToPort: int | None class Egress(TypedDict): - IpProtocol: Optional[str] - CidrIp: Optional[str] - CidrIpv6: Optional[str] - Description: Optional[str] - DestinationPrefixListId: Optional[str] - DestinationSecurityGroupId: Optional[str] - FromPort: Optional[int] - ToPort: Optional[int] + IpProtocol: str | None + CidrIp: str | None + CidrIpv6: str | None + Description: str | None + DestinationPrefixListId: str | None + DestinationSecurityGroupId: str | None + FromPort: int | None + ToPort: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py index 176bddb74e703..c867891dbc50b 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_securitygroup_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2SecurityGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::SecurityGroup" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_securitygroup import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py index e7c82a0d3669c..2b37495466411 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,33 +16,33 @@ class EC2SubnetProperties(TypedDict): - VpcId: Optional[str] - AssignIpv6AddressOnCreation: Optional[bool] - AvailabilityZone: Optional[str] - AvailabilityZoneId: Optional[str] - CidrBlock: Optional[str] - EnableDns64: Optional[bool] - Ipv6CidrBlock: Optional[str] - Ipv6CidrBlocks: Optional[list[str]] - Ipv6Native: Optional[bool] - MapPublicIpOnLaunch: Optional[bool] - NetworkAclAssociationId: Optional[str] - OutpostArn: Optional[str] - PrivateDnsNameOptionsOnLaunch: Optional[dict] - SubnetId: Optional[str] - Tags: Optional[list[Tag]] + VpcId: str | None + AssignIpv6AddressOnCreation: bool | None + AvailabilityZone: str | None + AvailabilityZoneId: str | None + CidrBlock: str | None + EnableDns64: bool | None + Ipv6CidrBlock: str | None + Ipv6CidrBlocks: list[str] | None + Ipv6Native: bool | None + MapPublicIpOnLaunch: bool | None + NetworkAclAssociationId: str | None + OutpostArn: str | None + PrivateDnsNameOptionsOnLaunch: dict | None + SubnetId: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" def generate_subnet_read_payload( - ec2_client, schema, subnet_ids: Optional[list[str]] = None + ec2_client, schema, subnet_ids: list[str] | None = None ) -> list[EC2SubnetProperties]: kwargs = {} if subnet_ids: diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py index 65349afd2f656..1efd975b0696c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnet_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2SubnetProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::Subnet" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_subnet import EC2SubnetProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py index d07bbdcb6665e..9b5f527b47d4c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,9 +14,9 @@ class EC2SubnetRouteTableAssociationProperties(TypedDict): - RouteTableId: Optional[str] - SubnetId: Optional[str] - Id: Optional[str] + RouteTableId: str | None + SubnetId: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py index 6841f27741847..7f76157a67283 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_subnetroutetableassociation_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2SubnetRouteTableAssociationProviderPlugin(CloudFormationResourceProvide name = "AWS::EC2::SubnetRouteTableAssociation" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_subnetroutetableassociation import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py index 4a4b5825966cc..b6488e2410bd8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,24 +14,24 @@ class EC2TransitGatewayProperties(TypedDict): - AmazonSideAsn: Optional[int] - AssociationDefaultRouteTableId: Optional[str] - AutoAcceptSharedAttachments: Optional[str] - DefaultRouteTableAssociation: Optional[str] - DefaultRouteTablePropagation: Optional[str] - Description: Optional[str] - DnsSupport: Optional[str] - Id: Optional[str] - MulticastSupport: Optional[str] - PropagationDefaultRouteTableId: Optional[str] - Tags: Optional[list[Tag]] - TransitGatewayCidrBlocks: Optional[list[str]] - VpnEcmpSupport: Optional[str] + AmazonSideAsn: int | None + AssociationDefaultRouteTableId: str | None + AutoAcceptSharedAttachments: str | None + DefaultRouteTableAssociation: str | None + DefaultRouteTablePropagation: str | None + Description: str | None + DnsSupport: str | None + Id: str | None + MulticastSupport: str | None + PropagationDefaultRouteTableId: str | None + Tags: list[Tag] | None + TransitGatewayCidrBlocks: list[str] | None + VpnEcmpSupport: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py index eac947d512bd5..916d4a9daec47 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgateway_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2TransitGatewayProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::TransitGateway" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_transitgateway import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py index 59aac3a6a15d4..e022ea2aa0bce 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class EC2TransitGatewayAttachmentProperties(TypedDict): - SubnetIds: Optional[list[str]] - TransitGatewayId: Optional[str] - VpcId: Optional[str] - Id: Optional[str] - Options: Optional[dict] - Tags: Optional[list[Tag]] + SubnetIds: list[str] | None + TransitGatewayId: str | None + VpcId: str | None + Id: str | None + Options: dict | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py index 7b34a535f56e6..331cecf66c630 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_transitgatewayattachment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2TransitGatewayAttachmentProviderPlugin(CloudFormationResourceProviderPl name = "AWS::EC2::TransitGatewayAttachment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_transitgatewayattachment import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py index 3244a72b8b863..e1e374d936922 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc.py @@ -3,7 +3,7 @@ import logging from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,23 +17,23 @@ class EC2VPCProperties(TypedDict): - CidrBlock: Optional[str] - CidrBlockAssociations: Optional[list[str]] - DefaultNetworkAcl: Optional[str] - DefaultSecurityGroup: Optional[str] - EnableDnsHostnames: Optional[bool] - EnableDnsSupport: Optional[bool] - InstanceTenancy: Optional[str] - Ipv4IpamPoolId: Optional[str] - Ipv4NetmaskLength: Optional[int] - Ipv6CidrBlocks: Optional[list[str]] - Tags: Optional[list[Tag]] - VpcId: Optional[str] + CidrBlock: str | None + CidrBlockAssociations: list[str] | None + DefaultNetworkAcl: str | None + DefaultSecurityGroup: str | None + EnableDnsHostnames: bool | None + EnableDnsSupport: bool | None + InstanceTenancy: str | None + Ipv4IpamPoolId: str | None + Ipv4NetmaskLength: int | None + Ipv6CidrBlocks: list[str] | None + Tags: list[Tag] | None + VpcId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py index 3f4aea38386f0..786ddc69de131 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpc_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2VPCProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::VPC" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_vpc import EC2VPCProvider diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py index 420efcb8029ee..6f5656e86728c 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint.py @@ -1,8 +1,9 @@ # LocalStack Resource Provider Scaffolding v2 from __future__ import annotations +import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +15,18 @@ class EC2VPCEndpointProperties(TypedDict): - ServiceName: Optional[str] - VpcId: Optional[str] - CreationTimestamp: Optional[str] - DnsEntries: Optional[list[str]] - Id: Optional[str] - NetworkInterfaceIds: Optional[list[str]] - PolicyDocument: Optional[str | dict] - PrivateDnsEnabled: Optional[bool] - RouteTableIds: Optional[list[str]] - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] - VpcEndpointType: Optional[str] + ServiceName: str | None + VpcId: str | None + CreationTimestamp: str | None + DnsEntries: list[str] | None + Id: str | None + NetworkInterfaceIds: list[str] | None + PolicyDocument: str | dict | None + PrivateDnsEnabled: bool | None + RouteTableIds: list[str] | None + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None + VpcEndpointType: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -66,12 +67,14 @@ def create( """ model = request.desired_state + ec2 = request.aws_client_factory.ec2 + create_params = util.select_attributes( - model, - [ - "PolidyDocument", + model=model, + params=[ + "PolicyDocument", "PrivateDnsEnabled", - "RouteTablesIds", + "RouteTableIds", "SecurityGroupIds", "ServiceName", "SubnetIds", @@ -80,12 +83,18 @@ def create( ], ) + if policy := model.get("PolicyDocument"): + create_params["PolicyDocument"] = json.dumps(policy) + if not request.custom_context.get(REPEATED_INVOCATION): - response = request.aws_client_factory.ec2.create_vpc_endpoint(**create_params) + response = ec2.create_vpc_endpoint(**create_params) model["Id"] = response["VpcEndpoint"]["VpcEndpointId"] model["DnsEntries"] = response["VpcEndpoint"]["DnsEntries"] - model["CreationTimestamp"] = response["VpcEndpoint"]["CreationTimestamp"] + model["CreationTimestamp"] = response["VpcEndpoint"]["CreationTimestamp"].isoformat() model["NetworkInterfaceIds"] = response["VpcEndpoint"]["NetworkInterfaceIds"] + model["VpcEndpointType"] = model.get("VpcEndpointType") or "Gateway" + model["PrivateDnsEnabled"] = bool(model.get("VpcEndpointType", False)) + request.custom_context[REPEATED_INVOCATION] = True return ProgressEvent( status=OperationStatus.IN_PROGRESS, @@ -93,9 +102,7 @@ def create( custom_context=request.custom_context, ) - response = request.aws_client_factory.ec2.describe_vpc_endpoints( - VpcEndpointIds=[model["Id"]] - ) + response = ec2.describe_vpc_endpoints(VpcEndpointIds=[model["Id"]]) if not response["VpcEndpoints"]: return ProgressEvent( status=OperationStatus.FAILED, diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py index e0e1d228a95de..a865440caed43 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcendpoint_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2VPCEndpointProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::EC2::VPCEndpoint" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_vpcendpoint import ( diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py index 6f12de474e359..a770bb3071cd8 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class EC2VPCGatewayAttachmentProperties(TypedDict): - VpcId: Optional[str] - Id: Optional[str] - InternetGatewayId: Optional[str] - VpnGatewayId: Optional[str] + VpcId: str | None + Id: str | None + InternetGatewayId: str | None + VpnGatewayId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py index f210fa0ff8c1d..b6c84cab930c5 100644 --- a/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py +++ b/localstack-core/localstack/services/ec2/resource_providers/aws_ec2_vpcgatewayattachment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EC2VPCGatewayAttachmentProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::EC2::VPCGatewayAttachment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ec2.resource_providers.aws_ec2_vpcgatewayattachment import ( diff --git a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py index a42735467d146..d0e5aefb2e1ba 100644 --- a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py +++ b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository.py @@ -3,7 +3,7 @@ import logging from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.constants import AWS_REGION_US_EAST_1, DEFAULT_AWS_ACCOUNT_ID @@ -22,34 +22,34 @@ class ECRRepositoryProperties(TypedDict): - Arn: Optional[str] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ImageScanningConfiguration: Optional[ImageScanningConfiguration] - ImageTagMutability: Optional[str] - LifecyclePolicy: Optional[LifecyclePolicy] - RepositoryName: Optional[str] - RepositoryPolicyText: Optional[dict | str] - RepositoryUri: Optional[str] - Tags: Optional[list[Tag]] + Arn: str | None + EncryptionConfiguration: EncryptionConfiguration | None + ImageScanningConfiguration: ImageScanningConfiguration | None + ImageTagMutability: str | None + LifecyclePolicy: LifecyclePolicy | None + RepositoryName: str | None + RepositoryPolicyText: dict | str | None + RepositoryUri: str | None + Tags: list[Tag] | None class LifecyclePolicy(TypedDict): - LifecyclePolicyText: Optional[str] - RegistryId: Optional[str] + LifecyclePolicyText: str | None + RegistryId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ImageScanningConfiguration(TypedDict): - ScanOnPush: Optional[bool] + ScanOnPush: bool | None class EncryptionConfiguration(TypedDict): - EncryptionType: Optional[str] - KmsKey: Optional[str] + EncryptionType: str | None + KmsKey: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py index 7d7ba440a668d..5f00d944024a5 100644 --- a/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py +++ b/localstack-core/localstack/services/ecr/resource_providers/aws_ecr_repository_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ECRRepositoryProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ECR::Repository" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ecr.resource_providers.aws_ecr_repository import ( diff --git a/localstack-core/localstack/services/edge.py b/localstack-core/localstack/services/edge.py index 5c3ede66b65e5..92c291bb30d9a 100644 --- a/localstack-core/localstack/services/edge.py +++ b/localstack-core/localstack/services/edge.py @@ -3,7 +3,7 @@ import shlex import subprocess import sys -from typing import List, Optional, TypeVar +from typing import TypeVar from localstack import config, constants from localstack.config import HostAndPort @@ -32,7 +32,7 @@ def do_start_edge( - listen: HostAndPort | List[HostAndPort], use_ssl: bool, asynchronous: bool = False + listen: HostAndPort | list[HostAndPort], use_ssl: bool, asynchronous: bool = False ): from localstack.aws.serving.edge import serve_gateway @@ -50,7 +50,7 @@ def can_use_sudo(): def ensure_can_use_sudo(): if not is_root() and not can_use_sudo(): if not sys.stdin.isatty(): - raise IOError("cannot get sudo password from non-tty input") + raise OSError("cannot get sudo password from non-tty input") print("Please enter your sudo password (required to configure local network):") run("sudo -v", stdin=True) @@ -166,7 +166,7 @@ def start_edge(listen_str: str, use_ssl: bool = True, asynchronous: bool = False def run_module_as_sudo( - module: str, arguments: Optional[List[str]] = None, asynchronous=False, env_vars=None + module: str, arguments: list[str] | None = None, asynchronous=False, env_vars=None ): # prepare environment env_vars = env_vars or {} @@ -195,7 +195,7 @@ def run_command(*_): return result -def parse_gateway_listen(listen: str, default_host: str, default_port: int) -> List[HostAndPort]: +def parse_gateway_listen(listen: str, default_host: str, default_port: int) -> list[HostAndPort]: addresses = [] for address in listen.split(","): addresses.append(HostAndPort.parse(address, default_host, default_port)) diff --git a/localstack-core/localstack/services/es/provider.py b/localstack-core/localstack/services/es/provider.py index 4519e417bceaa..0188b19ca8ba3 100644 --- a/localstack-core/localstack/services/es/provider.py +++ b/localstack-core/localstack/services/es/provider.py @@ -1,5 +1,5 @@ from contextlib import contextmanager -from typing import Dict, Optional, cast +from typing import cast from botocore.exceptions import ClientError @@ -71,8 +71,8 @@ def _version_to_opensearch( - version: Optional[ElasticsearchVersionString], -) -> Optional[VersionString]: + version: ElasticsearchVersionString | None, +) -> VersionString | None: if version is not None: if version.startswith("OpenSearch_"): return version @@ -81,8 +81,8 @@ def _version_to_opensearch( def _version_from_opensearch( - version: Optional[VersionString], -) -> Optional[ElasticsearchVersionString]: + version: VersionString | None, +) -> ElasticsearchVersionString | None: if version is not None: if version.startswith("Elasticsearch_"): return version.split("_")[1] @@ -90,19 +90,19 @@ def _version_from_opensearch( return version -def _instancetype_to_opensearch(instance_type: Optional[str]) -> Optional[str]: +def _instancetype_to_opensearch(instance_type: str | None) -> str | None: if instance_type is not None: return instance_type.replace("elasticsearch", "search") -def _instancetype_from_opensearch(instance_type: Optional[str]) -> Optional[str]: +def _instancetype_from_opensearch(instance_type: str | None) -> str | None: if instance_type is not None: return instance_type.replace("search", "elasticsearch") def _clusterconfig_from_opensearch( - cluster_config: Optional[ClusterConfig], -) -> Optional[ElasticsearchClusterConfig]: + cluster_config: ClusterConfig | None, +) -> ElasticsearchClusterConfig | None: if cluster_config is not None: # Just take the whole typed dict and typecast it to our target type result = cast(ElasticsearchClusterConfig, cluster_config) @@ -117,8 +117,8 @@ def _clusterconfig_from_opensearch( def _domainstatus_from_opensearch( - domain_status: Optional[DomainStatus], -) -> Optional[ElasticsearchDomainStatus]: + domain_status: DomainStatus | None, +) -> ElasticsearchDomainStatus | None: if domain_status is not None: # Just take the whole typed dict and typecast it to our target type result = cast(ElasticsearchDomainStatus, domain_status) @@ -135,8 +135,8 @@ def _domainstatus_from_opensearch( def _clusterconfig_to_opensearch( - elasticsearch_cluster_config: Optional[ElasticsearchClusterConfig], -) -> Optional[ClusterConfig]: + elasticsearch_cluster_config: ElasticsearchClusterConfig | None, +) -> ClusterConfig | None: if elasticsearch_cluster_config is not None: result = cast(ClusterConfig, elasticsearch_cluster_config) if instance_type := result.get("InstanceType"): @@ -149,8 +149,8 @@ def _clusterconfig_to_opensearch( def _domainconfig_from_opensearch( - domain_config: Optional[DomainConfig], -) -> Optional[ElasticsearchDomainConfig]: + domain_config: DomainConfig | None, +) -> ElasticsearchDomainConfig | None: if domain_config is not None: result = cast(ElasticsearchDomainConfig, domain_config) engine_version = domain_config.get("EngineVersion", {}) @@ -169,8 +169,8 @@ def _domainconfig_from_opensearch( def _compatible_version_list_from_opensearch( - compatible_version_list: Optional[CompatibleVersionsList], -) -> Optional[CompatibleElasticsearchVersionsList]: + compatible_version_list: CompatibleVersionsList | None, +) -> CompatibleElasticsearchVersionsList | None: if compatible_version_list is not None: return [ CompatibleVersionsMap( @@ -304,7 +304,7 @@ def update_elasticsearch_domain_config( region_name=context.region, aws_access_key_id=context.account_id ).opensearch - payload: Dict + payload: dict if "ElasticsearchClusterConfig" in payload: payload["ClusterConfig"] = payload["ElasticsearchClusterConfig"] payload["ClusterConfig"]["InstanceType"] = _instancetype_to_opensearch( @@ -347,7 +347,7 @@ def list_domain_names( with exception_mapper(): domain_names = opensearch_client.list_domain_names(**kwargs)["DomainNames"] - return ListDomainNamesResponse(DomainNames=cast(Optional[DomainInfoList], domain_names)) + return ListDomainNamesResponse(DomainNames=cast(DomainInfoList | None, domain_names)) def list_elasticsearch_versions( self, diff --git a/localstack-core/localstack/services/events/archive.py b/localstack-core/localstack/services/events/archive.py index 12d7e4601747f..36d0db5394605 100644 --- a/localstack-core/localstack/services/events/archive.py +++ b/localstack-core/localstack/services/events/archive.py @@ -1,6 +1,6 @@ import json import logging -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Self from botocore.client import BaseClient @@ -95,7 +95,7 @@ def set_state(self, state: ArchiveState) -> None: self.archive.state = state def set_creation_time(self) -> None: - self.archive.creation_time = datetime.now(timezone.utc) + self.archive.creation_time = datetime.now(UTC) def update( self, diff --git a/localstack-core/localstack/services/events/connection.py b/localstack-core/localstack/services/events/connection.py index c2b72a2025328..709393d626968 100644 --- a/localstack-core/localstack/services/events/connection.py +++ b/localstack-core/localstack/services/events/connection.py @@ -2,7 +2,7 @@ import logging import re import uuid -from datetime import datetime, timezone +from datetime import UTC, datetime from localstack.aws.api.events import ( Arn, @@ -138,7 +138,7 @@ def update( auth_parameters, ) self.connection.secret_arn = secret_arn - self.connection.last_authorized_time = datetime.now(timezone.utc) + self.connection.last_authorized_time = datetime.now(UTC) # Set new values self.connection.authorization_type = auth_type @@ -149,7 +149,7 @@ def update( ) self.connection.auth_parameters = public_auth_parameters self.set_state(ConnectionState.AUTHORIZED) - self.connection.last_modified_time = datetime.now(timezone.utc) + self.connection.last_modified_time = datetime.now(UTC) except Exception as error: LOG.warning( @@ -162,7 +162,7 @@ def delete(self) -> None: self.set_state(ConnectionState.DELETING) self.delete_connection_secret(self.connection.secret_arn) self.set_state(ConnectionState.DELETING) # required for AWS parity - self.connection.last_modified_time = datetime.now(timezone.utc) + self.connection.last_modified_time = datetime.now(UTC) def create_connection_secret( self, @@ -204,7 +204,7 @@ def update_connection_secret( try: secretsmanager_client.update_secret(SecretId=secret_arn, SecretString=secret_value) self.set_state(ConnectionState.AUTHORIZED) - self.connection.last_authorized_time = datetime.now(timezone.utc) + self.connection.last_authorized_time = datetime.now(UTC) except Exception as error: LOG.warning("Secret with id %s updating failed with errors: %s.", secret_arn, error) diff --git a/localstack-core/localstack/services/events/event_bus.py b/localstack-core/localstack/services/events/event_bus.py index 1ea6f332a493b..19a4657233b4c 100644 --- a/localstack-core/localstack/services/events/event_bus.py +++ b/localstack-core/localstack/services/events/event_bus.py @@ -1,6 +1,6 @@ import json -from datetime import datetime, timezone -from typing import Optional, Self +from datetime import UTC, datetime +from typing import Self from localstack.aws.api.events import ( Action, @@ -34,11 +34,11 @@ def create_event_bus_service( name: EventBusName, region: str, account_id: str, - event_source_name: Optional[str] = None, - description: Optional[str] = None, - tags: Optional[TagList] = None, - policy: Optional[str] = None, - rules: Optional[RuleDict] = None, + event_source_name: str | None = None, + description: str | None = None, + tags: TagList | None = None, + policy: str | None = None, + rules: RuleDict | None = None, ) -> Self: return cls( EventBus( @@ -68,7 +68,7 @@ def put_permission( # TODO: cover via test # if policy and any([action, principal, statement_id, condition]): # raise ValueError("Combination of policy with other arguments is not allowed") - self.event_bus.last_modified_time = datetime.now(timezone.utc) + self.event_bus.last_modified_time = datetime.now(UTC) if policy: # policy document replaces all existing permissions policy = json.loads(policy) parsed_policy = ResourcePolicy(**policy) @@ -102,7 +102,7 @@ def revoke_put_events_permission(self, statement_id: str): for statement in policy["Statement"] if statement.get("Sid") != statement_id ] - self.event_bus.last_modified_time = datetime.now(timezone.utc) + self.event_bus.last_modified_time = datetime.now(UTC) def _parse_statement( self, diff --git a/localstack-core/localstack/services/events/models.py b/localstack-core/localstack/services/events/models.py index 95e64ece83711..4b1198b9673e5 100644 --- a/localstack-core/localstack/services/events/models.py +++ b/localstack-core/localstack/services/events/models.py @@ -1,8 +1,8 @@ import uuid from dataclasses import dataclass, field -from datetime import datetime, timezone +from datetime import UTC, datetime from enum import Enum -from typing import Literal, Optional, TypeAlias, TypedDict +from typing import Literal, TypeAlias, TypedDict from localstack.aws.api.core import ServiceException from localstack.aws.api.events import ( @@ -85,14 +85,14 @@ def __init__(self, reason=None, message=None) -> None: { "version": str, "id": str, - "detail-type": Optional[str], - "source": Optional[EventSourceName], + "detail-type": str | None, + "source": EventSourceName | None, "account": str, "time": EventTime, "region": str, - "resources": Optional[EventResourceList], + "resources": EventResourceList | None, "detail": dict[str, str | dict], - "replay-name": Optional[ReplayName], + "replay-name": ReplayName | None, "event-bus-name": EventBusName, }, ) @@ -134,15 +134,15 @@ class Rule: name: RuleName region: str account_id: str - schedule_expression: Optional[ScheduleExpression] = None - event_pattern: Optional[EventPattern] = None - state: Optional[RuleState] = None - description: Optional[RuleDescription] = None - role_arn: Optional[RoleArn] = None + schedule_expression: ScheduleExpression | None = None + event_pattern: EventPattern | None = None + state: RuleState | None = None + description: RuleDescription | None = None + role_arn: RoleArn | None = None tags: TagList = field(default_factory=list) event_bus_name: EventBusName = "default" targets: TargetDict = field(default_factory=dict) - managed_by: Optional[ManagedBy] = None # can only be set by AWS services + managed_by: ManagedBy | None = None # can only be set by AWS services created_by: CreatedBy = field(init=False) def __post_init__(self): @@ -171,12 +171,12 @@ class Replay: destination: ReplayDestination # Event Bus Arn or Rule Arns event_start_time: Timestamp event_end_time: Timestamp - description: Optional[ReplayDescription] = None - state: Optional[ReplayState] = None - state_reason: Optional[ReplayStateReason] = None - event_last_replayed_time: Optional[Timestamp] = None - replay_start_time: Optional[Timestamp] = None - replay_end_time: Optional[Timestamp] = None + description: ReplayDescription | None = None + state: ReplayState | None = None + state_reason: ReplayStateReason | None = None + event_last_replayed_time: Timestamp | None = None + replay_start_time: Timestamp | None = None + replay_end_time: Timestamp | None = None @property def arn(self) -> Arn: @@ -217,17 +217,17 @@ class EventBus: name: EventBusName region: str account_id: str - event_source_name: Optional[str] = None - description: Optional[str] = None + event_source_name: str | None = None + description: str | None = None tags: TagList = field(default_factory=list) - policy: Optional[ResourcePolicy] = None + policy: ResourcePolicy | None = None rules: RuleDict = field(default_factory=dict) creation_time: Timestamp = field(init=False) last_modified_time: Timestamp = field(init=False) def __post_init__(self): - self.creation_time = datetime.now(timezone.utc) - self.last_modified_time = datetime.now(timezone.utc) + self.creation_time = datetime.now(UTC) + self.last_modified_time = datetime.now(UTC) if self.rules is None: self.rules = {} if self.tags is None: @@ -259,7 +259,7 @@ class Connection: id: str = str(uuid.uuid4()) def __post_init__(self): - timestamp_now = datetime.now(timezone.utc) + timestamp_now = datetime.now(UTC) self.creation_time = timestamp_now self.last_modified_time = timestamp_now self.last_authorized_time = timestamp_now @@ -292,7 +292,7 @@ class ApiDestination: id: str = str(short_uid()) def __post_init__(self): - timestamp_now = datetime.now(timezone.utc) + timestamp_now = datetime.now(UTC) self.creation_time = timestamp_now self.last_modified_time = timestamp_now self.last_authorized_time = timestamp_now diff --git a/localstack-core/localstack/services/events/provider.py b/localstack-core/localstack/services/events/provider.py index ca6a430b8dea6..8931c325ab96a 100644 --- a/localstack-core/localstack/services/events/provider.py +++ b/localstack-core/localstack/services/events/provider.py @@ -2,7 +2,7 @@ import json import logging import re -from typing import Callable, Optional +from collections.abc import Callable from localstack.aws.api import RequestContext, handler from localstack.aws.api.config import TagsList @@ -1349,9 +1349,9 @@ def create_event_bus_service( name: EventBusName, region: str, account_id: str, - event_source_name: Optional[EventSourceName], - description: Optional[EventBusDescription], - tags: Optional[TagList], + event_source_name: EventSourceName | None, + description: EventBusDescription | None, + tags: TagList | None, ) -> EventBusService: event_bus_service = EventBusService.create_event_bus_service( name, @@ -1369,14 +1369,14 @@ def create_rule_service( name: RuleName, region: str, account_id: str, - schedule_expression: Optional[ScheduleExpression], - event_pattern: Optional[EventPattern], - state: Optional[RuleState], - description: Optional[RuleDescription], - role_arn: Optional[RoleArn], - tags: Optional[TagList], - event_bus_name: Optional[EventBusName], - targets: Optional[TargetDict], + schedule_expression: ScheduleExpression | None, + event_pattern: EventPattern | None, + state: RuleState | None, + description: RuleDescription | None, + role_arn: RoleArn | None, + tags: TagList | None, + event_bus_name: EventBusName | None, + targets: TargetDict | None, ) -> RuleService: rule_service = RuleService.create_rule_service( name, diff --git a/localstack-core/localstack/services/events/replay.py b/localstack-core/localstack/services/events/replay.py index 7a58fb3534d05..6b4579453890b 100644 --- a/localstack-core/localstack/services/events/replay.py +++ b/localstack-core/localstack/services/events/replay.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from localstack.aws.api.events import ( Arn, @@ -61,13 +61,13 @@ def set_state(self, state: ReplayState) -> None: def start(self, events: FormattedEventList | None) -> None: self.set_state(ReplayState.RUNNING) - self.replay.replay_start_time = datetime.now(timezone.utc) + self.replay.replay_start_time = datetime.now(UTC) if events: self._set_event_last_replayed_time(events) def finish(self) -> None: self.set_state(ReplayState.COMPLETED) - self.replay.replay_end_time = datetime.now(timezone.utc) + self.replay.replay_end_time = datetime.now(UTC) def stop(self) -> None: self.set_state(ReplayState.CANCELLING) diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py index 372d45de40dce..7d62bcc0e4be1 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,13 +14,13 @@ class EventsApiDestinationProperties(TypedDict): - ConnectionArn: Optional[str] - HttpMethod: Optional[str] - InvocationEndpoint: Optional[str] - Arn: Optional[str] - Description: Optional[str] - InvocationRateLimitPerSecond: Optional[int] - Name: Optional[str] + ConnectionArn: str | None + HttpMethod: str | None + InvocationEndpoint: str | None + Arn: str | None + Description: str | None + InvocationRateLimitPerSecond: int | None + Name: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py index 0aa7ada08cc50..5e9c09f9e4b31 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_apidestination_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsApiDestinationProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::ApiDestination" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_apidestination import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py b/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py index a99f8df743aca..2802a0b17239c 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_connection.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,53 +14,53 @@ class EventsConnectionProperties(TypedDict): - AuthParameters: Optional[AuthParameters] - AuthorizationType: Optional[str] - Arn: Optional[str] - Description: Optional[str] - Name: Optional[str] - SecretArn: Optional[str] + AuthParameters: AuthParameters | None + AuthorizationType: str | None + Arn: str | None + Description: str | None + Name: str | None + SecretArn: str | None class ApiKeyAuthParameters(TypedDict): - ApiKeyName: Optional[str] - ApiKeyValue: Optional[str] + ApiKeyName: str | None + ApiKeyValue: str | None class BasicAuthParameters(TypedDict): - Password: Optional[str] - Username: Optional[str] + Password: str | None + Username: str | None class ClientParameters(TypedDict): - ClientID: Optional[str] - ClientSecret: Optional[str] + ClientID: str | None + ClientSecret: str | None class Parameter(TypedDict): - Key: Optional[str] - Value: Optional[str] - IsValueSecret: Optional[bool] + Key: str | None + Value: str | None + IsValueSecret: bool | None class ConnectionHttpParameters(TypedDict): - BodyParameters: Optional[list[Parameter]] - HeaderParameters: Optional[list[Parameter]] - QueryStringParameters: Optional[list[Parameter]] + BodyParameters: list[Parameter] | None + HeaderParameters: list[Parameter] | None + QueryStringParameters: list[Parameter] | None class OAuthParameters(TypedDict): - AuthorizationEndpoint: Optional[str] - ClientParameters: Optional[ClientParameters] - HttpMethod: Optional[str] - OAuthHttpParameters: Optional[ConnectionHttpParameters] + AuthorizationEndpoint: str | None + ClientParameters: ClientParameters | None + HttpMethod: str | None + OAuthHttpParameters: ConnectionHttpParameters | None class AuthParameters(TypedDict): - ApiKeyAuthParameters: Optional[ApiKeyAuthParameters] - BasicAuthParameters: Optional[BasicAuthParameters] - InvocationHttpParameters: Optional[ConnectionHttpParameters] - OAuthParameters: Optional[OAuthParameters] + ApiKeyAuthParameters: ApiKeyAuthParameters | None + BasicAuthParameters: BasicAuthParameters | None + InvocationHttpParameters: ConnectionHttpParameters | None + OAuthParameters: OAuthParameters | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py index c8b16c6c961a1..652ac6d0a861e 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_connection_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsConnectionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::Connection" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_connection import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py index 5929d42f7252b..74f0e654e9bf4 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class EventsEventBusProperties(TypedDict): - Name: Optional[str] - Arn: Optional[str] - EventSourceName: Optional[str] - Id: Optional[str] - Policy: Optional[str] - Tags: Optional[list[TagEntry]] + Name: str | None + Arn: str | None + EventSourceName: str | None + Id: str | None + Policy: str | None + Tags: list[TagEntry] | None class TagEntry(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py index 25f94f1940bb2..0be6346557600 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbus_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsEventBusProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::EventBus" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_eventbus import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py index 9da54ceeff6bf..891809580b2e8 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -18,19 +18,19 @@ class EventsEventBusPolicyProperties(TypedDict): - StatementId: Optional[str] - Action: Optional[str] - Condition: Optional[Condition] - EventBusName: Optional[str] - Id: Optional[str] - Principal: Optional[str] - Statement: Optional[dict] + StatementId: str | None + Action: str | None + Condition: Condition | None + EventBusName: str | None + Id: str | None + Principal: str | None + Statement: dict | None class Condition(TypedDict): - Key: Optional[str] - Type: Optional[str] - Value: Optional[str] + Key: str | None + Type: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py index 5368348690773..e835ffb41f488 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_eventbuspolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsEventBusPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::EventBusPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_eventbuspolicy import ( diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py b/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py index a10d23360a41c..6bca937fc47ac 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_rule.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,153 +16,153 @@ class EventsRuleProperties(TypedDict): - Arn: Optional[str] - Description: Optional[str] - EventBusName: Optional[str] - EventPattern: Optional[dict] - Id: Optional[str] - Name: Optional[str] - RoleArn: Optional[str] - ScheduleExpression: Optional[str] - State: Optional[str] - Targets: Optional[list[Target]] + Arn: str | None + Description: str | None + EventBusName: str | None + EventPattern: dict | None + Id: str | None + Name: str | None + RoleArn: str | None + ScheduleExpression: str | None + State: str | None + Targets: list[Target] | None class HttpParameters(TypedDict): - HeaderParameters: Optional[dict] - PathParameterValues: Optional[list[str]] - QueryStringParameters: Optional[dict] + HeaderParameters: dict | None + PathParameterValues: list[str] | None + QueryStringParameters: dict | None class DeadLetterConfig(TypedDict): - Arn: Optional[str] + Arn: str | None class RunCommandTarget(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class RunCommandParameters(TypedDict): - RunCommandTargets: Optional[list[RunCommandTarget]] + RunCommandTargets: list[RunCommandTarget] | None class InputTransformer(TypedDict): - InputTemplate: Optional[str] - InputPathsMap: Optional[dict] + InputTemplate: str | None + InputPathsMap: dict | None class KinesisParameters(TypedDict): - PartitionKeyPath: Optional[str] + PartitionKeyPath: str | None class RedshiftDataParameters(TypedDict): - Database: Optional[str] - Sql: Optional[str] - DbUser: Optional[str] - SecretManagerArn: Optional[str] - StatementName: Optional[str] - WithEvent: Optional[bool] + Database: str | None + Sql: str | None + DbUser: str | None + SecretManagerArn: str | None + StatementName: str | None + WithEvent: bool | None class SqsParameters(TypedDict): - MessageGroupId: Optional[str] + MessageGroupId: str | None class PlacementConstraint(TypedDict): - Expression: Optional[str] - Type: Optional[str] + Expression: str | None + Type: str | None class PlacementStrategy(TypedDict): - Field: Optional[str] - Type: Optional[str] + Field: str | None + Type: str | None class CapacityProviderStrategyItem(TypedDict): - CapacityProvider: Optional[str] - Base: Optional[int] - Weight: Optional[int] + CapacityProvider: str | None + Base: int | None + Weight: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class AwsVpcConfiguration(TypedDict): - Subnets: Optional[list[str]] - AssignPublicIp: Optional[str] - SecurityGroups: Optional[list[str]] + Subnets: list[str] | None + AssignPublicIp: str | None + SecurityGroups: list[str] | None class NetworkConfiguration(TypedDict): - AwsVpcConfiguration: Optional[AwsVpcConfiguration] + AwsVpcConfiguration: AwsVpcConfiguration | None class EcsParameters(TypedDict): - TaskDefinitionArn: Optional[str] - CapacityProviderStrategy: Optional[list[CapacityProviderStrategyItem]] - EnableECSManagedTags: Optional[bool] - EnableExecuteCommand: Optional[bool] - Group: Optional[str] - LaunchType: Optional[str] - NetworkConfiguration: Optional[NetworkConfiguration] - PlacementConstraints: Optional[list[PlacementConstraint]] - PlacementStrategies: Optional[list[PlacementStrategy]] - PlatformVersion: Optional[str] - PropagateTags: Optional[str] - ReferenceId: Optional[str] - TagList: Optional[list[Tag]] - TaskCount: Optional[int] + TaskDefinitionArn: str | None + CapacityProviderStrategy: list[CapacityProviderStrategyItem] | None + EnableECSManagedTags: bool | None + EnableExecuteCommand: bool | None + Group: str | None + LaunchType: str | None + NetworkConfiguration: NetworkConfiguration | None + PlacementConstraints: list[PlacementConstraint] | None + PlacementStrategies: list[PlacementStrategy] | None + PlatformVersion: str | None + PropagateTags: str | None + ReferenceId: str | None + TagList: list[Tag] | None + TaskCount: int | None class BatchRetryStrategy(TypedDict): - Attempts: Optional[int] + Attempts: int | None class BatchArrayProperties(TypedDict): - Size: Optional[int] + Size: int | None class BatchParameters(TypedDict): - JobDefinition: Optional[str] - JobName: Optional[str] - ArrayProperties: Optional[BatchArrayProperties] - RetryStrategy: Optional[BatchRetryStrategy] + JobDefinition: str | None + JobName: str | None + ArrayProperties: BatchArrayProperties | None + RetryStrategy: BatchRetryStrategy | None class SageMakerPipelineParameter(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class SageMakerPipelineParameters(TypedDict): - PipelineParameterList: Optional[list[SageMakerPipelineParameter]] + PipelineParameterList: list[SageMakerPipelineParameter] | None class RetryPolicy(TypedDict): - MaximumEventAgeInSeconds: Optional[int] - MaximumRetryAttempts: Optional[int] + MaximumEventAgeInSeconds: int | None + MaximumRetryAttempts: int | None class Target(TypedDict): - Arn: Optional[str] - Id: Optional[str] - BatchParameters: Optional[BatchParameters] - DeadLetterConfig: Optional[DeadLetterConfig] - EcsParameters: Optional[EcsParameters] - HttpParameters: Optional[HttpParameters] - Input: Optional[str] - InputPath: Optional[str] - InputTransformer: Optional[InputTransformer] - KinesisParameters: Optional[KinesisParameters] - RedshiftDataParameters: Optional[RedshiftDataParameters] - RetryPolicy: Optional[RetryPolicy] - RoleArn: Optional[str] - RunCommandParameters: Optional[RunCommandParameters] - SageMakerPipelineParameters: Optional[SageMakerPipelineParameters] - SqsParameters: Optional[SqsParameters] + Arn: str | None + Id: str | None + BatchParameters: BatchParameters | None + DeadLetterConfig: DeadLetterConfig | None + EcsParameters: EcsParameters | None + HttpParameters: HttpParameters | None + Input: str | None + InputPath: str | None + InputTransformer: InputTransformer | None + KinesisParameters: KinesisParameters | None + RedshiftDataParameters: RedshiftDataParameters | None + RetryPolicy: RetryPolicy | None + RoleArn: str | None + RunCommandParameters: RunCommandParameters | None + SageMakerPipelineParameters: SageMakerPipelineParameters | None + SqsParameters: SqsParameters | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py b/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py index 3fa01b6717fdc..68fd6bd27831c 100644 --- a/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py +++ b/localstack-core/localstack/services/events/resource_providers/aws_events_rule_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class EventsRuleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Events::Rule" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.events.resource_providers.aws_events_rule import EventsRuleProvider diff --git a/localstack-core/localstack/services/events/rule.py b/localstack-core/localstack/services/events/rule.py index 576cfc36e781c..853005d8e3f2a 100644 --- a/localstack-core/localstack/services/events/rule.py +++ b/localstack-core/localstack/services/events/rule.py @@ -1,5 +1,5 @@ import re -from typing import Callable, Optional +from collections.abc import Callable from localstack.aws.api.events import ( Arn, @@ -75,17 +75,17 @@ def __init__(self, rule: Rule): def create_rule_service( cls, name: RuleName, - region: Optional[str] = None, - account_id: Optional[str] = None, - schedule_expression: Optional[ScheduleExpression] = None, - event_pattern: Optional[EventPattern] = None, - state: Optional[RuleState] = None, - description: Optional[RuleDescription] = None, - role_arn: Optional[RoleArn] = None, - tags: Optional[TagList] = None, - event_bus_name: Optional[EventBusName] = None, - targets: Optional[TargetDict] = None, - managed_by: Optional[ManagedBy] = None, + region: str | None = None, + account_id: str | None = None, + schedule_expression: ScheduleExpression | None = None, + event_pattern: EventPattern | None = None, + state: RuleState | None = None, + description: RuleDescription | None = None, + role_arn: RoleArn | None = None, + tags: TagList | None = None, + event_bus_name: EventBusName | None = None, + targets: TargetDict | None = None, + managed_by: ManagedBy | None = None, ): cls._validate_input(event_pattern, schedule_expression, event_bus_name) # required to keep data and functionality separate for persistence @@ -209,9 +209,9 @@ def validate_targets_input(self, targets: TargetList) -> PutTargetsResultEntryLi @classmethod def _validate_input( cls, - event_pattern: Optional[EventPattern], - schedule_expression: Optional[ScheduleExpression], - event_bus_name: Optional[EventBusName] = "default", + event_pattern: EventPattern | None, + schedule_expression: ScheduleExpression | None, + event_bus_name: EventBusName | None = "default", ) -> None: if not event_pattern and not schedule_expression: raise ValidationException( diff --git a/localstack-core/localstack/services/events/target.py b/localstack-core/localstack/services/events/target.py index fe18ce999412c..6ab0c6a7a2aaf 100644 --- a/localstack-core/localstack/services/events/target.py +++ b/localstack-core/localstack/services/events/target.py @@ -4,7 +4,7 @@ import re import uuid from abc import ABC, abstractmethod -from typing import Any, Dict, Set, Type +from typing import Any from urllib.parse import urlencode import requests @@ -59,7 +59,7 @@ } AWS_PREDEFINED_PLACEHOLDERS_JSON_VALUES = {"aws.events.event", "aws.events.event.json"} -PREDEFINED_PLACEHOLDERS: Set[str] = AWS_PREDEFINED_PLACEHOLDERS_STRING_VALUES.union( +PREDEFINED_PLACEHOLDERS: set[str] = AWS_PREDEFINED_PLACEHOLDERS_STRING_VALUES.union( AWS_PREDEFINED_PLACEHOLDERS_JSON_VALUES ) @@ -222,7 +222,7 @@ def transform_event_with_target_input_transformer( predefined_template_replacements = self._get_predefined_template_replacements(event) template_replacements.update(predefined_template_replacements) - is_json_template = input_template.strip().startswith(("{")) + is_json_template = input_template.strip().startswith("{") populated_template = replace_template_placeholders( input_template, template_replacements, is_json_template ) @@ -424,7 +424,7 @@ def _validate_input(self, target: Target): # if not collections.get_safe(target, "$.RoleArn"): # raise ValueError("RoleArn is required for ApiGateway target") - def _get_predefined_template_replacements(self, event: Dict[str, Any]) -> Dict[str, Any]: + def _get_predefined_template_replacements(self, event: dict[str, Any]) -> dict[str, Any]: """Extracts predefined values from the event.""" predefined_template_replacements = {} predefined_template_replacements["aws.events.rule-arn"] = self.rule_arn @@ -731,7 +731,7 @@ def __init__( self.account_id = account_id @classmethod - def register_target_sender(cls, service_name: str, sender_class: Type[TargetSender]): + def register_target_sender(cls, service_name: str, sender_class: type[TargetSender]): cls.target_map[service_name] = sender_class def get_target_sender(self) -> TargetSender: diff --git a/localstack-core/localstack/services/events/utils.py b/localstack-core/localstack/services/events/utils.py index 5ac8e835b136f..a162dc51b101a 100644 --- a/localstack-core/localstack/services/events/utils.py +++ b/localstack-core/localstack/services/events/utils.py @@ -1,8 +1,8 @@ import json import logging import re -from datetime import datetime, timezone -from typing import Any, Dict, Optional +from datetime import UTC, datetime +from typing import Any from botocore.utils import ArnParser @@ -57,7 +57,7 @@ def default(self, obj): return super().default(obj) -def to_json_str(obj: Any, separators: Optional[tuple[str, str]] = (",", ":")) -> str: +def to_json_str(obj: Any, separators: tuple[str, str] | None = (",", ":")) -> str: return json.dumps(obj, cls=EventJSONEncoder, separators=separators) @@ -132,11 +132,11 @@ def get_resource_type(arn: Arn) -> ResourceType: def get_event_time(event: PutEventsRequestEntry) -> EventTime: - event_time = datetime.now(timezone.utc) + event_time = datetime.now(UTC) if event_timestamp := event.get("Time"): try: # use time from event if provided - event_time = event_timestamp.replace(tzinfo=timezone.utc) + event_time = event_timestamp.replace(tzinfo=UTC) except ValueError: # use current time if event time is invalid LOG.debug( @@ -154,11 +154,11 @@ def convert_to_timezone_aware_datetime( timestamp: Timestamp, ) -> Timestamp: if timestamp.tzinfo is None: - timestamp = timestamp.replace(tzinfo=timezone.utc) + timestamp = timestamp.replace(tzinfo=UTC) return timestamp -def recursive_remove_none_values_from_dict(d: Dict[str, Any]) -> Dict[str, Any]: +def recursive_remove_none_values_from_dict(d: dict[str, Any]) -> dict[str, Any]: """ Recursively removes keys with non values from a dictionary. """ diff --git a/localstack-core/localstack/services/events/v1/models.py b/localstack-core/localstack/services/events/v1/models.py index 4096215c82499..edf99a8a35537 100644 --- a/localstack-core/localstack/services/events/v1/models.py +++ b/localstack-core/localstack/services/events/v1/models.py @@ -1,11 +1,9 @@ -from typing import Dict - from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute class EventsStore(BaseStore): # maps rule name to job_id - rule_scheduled_jobs: Dict[str, str] = LocalAttribute(default=dict) + rule_scheduled_jobs: dict[str, str] = LocalAttribute(default=dict) events_stores = AccountRegionBundle("events", EventsStore) diff --git a/localstack-core/localstack/services/events/v1/provider.py b/localstack-core/localstack/services/events/v1/provider.py index 9e3da8e447f6a..a98fc0c17fabc 100644 --- a/localstack-core/localstack/services/events/v1/provider.py +++ b/localstack-core/localstack/services/events/v1/provider.py @@ -4,7 +4,7 @@ import os import re import time -from typing import Any, Dict, Optional +from typing import Any from moto.events import events_backends from moto.events.responses import EventsHandler as MotoEventsHandler @@ -119,7 +119,7 @@ def test_event_pattern( def get_scheduled_rule_func( store: EventsStore, rule_name: RuleName, - event_bus_name_or_arn: Optional[EventBusNameOrArn] = None, + event_bus_name_or_arn: EventBusNameOrArn | None = None, ): def func(*args, **kwargs): account_id = store._account_id @@ -217,10 +217,10 @@ def convert_schedule_to_cron(schedule): @staticmethod def put_rule_job_scheduler( store: EventsStore, - name: Optional[RuleName], - state: Optional[RuleState], - schedule_expression: Optional[ScheduleExpression], - event_bus_name_or_arn: Optional[EventBusNameOrArn] = None, + name: RuleName | None, + state: RuleState | None, + schedule_expression: ScheduleExpression | None, + event_bus_name_or_arn: EventBusNameOrArn | None = None, ): if not schedule_expression: return @@ -398,14 +398,14 @@ def filter_event_based_on_event_format( return True -def filter_event_with_target_input_path(target: Dict, event: Dict) -> Dict: +def filter_event_with_target_input_path(target: dict, event: dict) -> dict: input_path = target.get("InputPath") if input_path: event = extract_jsonpath(event, input_path) return event -def process_event_with_input_transformer(input_transformer: Dict, event: Dict) -> Dict: +def process_event_with_input_transformer(input_transformer: dict, event: dict) -> dict: """ Process the event with the input transformer of the target event, by replacing the message with the populated InputTemplate. @@ -426,7 +426,7 @@ def process_event_with_input_transformer(input_transformer: Dict, event: Dict) - return templated_event -def process_events(event: Dict, targets: list[Dict]): +def process_events(event: dict, targets: list[dict]): for target in targets: arn = target["Arn"] changed_event = filter_event_with_target_input_path(target, event) @@ -453,7 +453,7 @@ def process_events(event: Dict, targets: list[Dict]): ) -def get_event_bus_name(event_bus_name_or_arn: Optional[EventBusNameOrArn] = None) -> str: +def get_event_bus_name(event_bus_name_or_arn: EventBusNameOrArn | None = None) -> str: event_bus_name_or_arn = event_bus_name_or_arn or DEFAULT_EVENT_BUS_NAME return event_bus_name_or_arn.split("/")[-1] diff --git a/localstack-core/localstack/services/firehose/models.py b/localstack-core/localstack/services/firehose/models.py index ef2e395ef9229..f4e1a665e3c65 100644 --- a/localstack-core/localstack/services/firehose/models.py +++ b/localstack-core/localstack/services/firehose/models.py @@ -1,5 +1,3 @@ -from typing import Dict - from localstack.aws.api.firehose import DeliveryStreamDescription from localstack.services.stores import ( AccountRegionBundle, @@ -12,7 +10,7 @@ class FirehoseStore(BaseStore): # maps delivery stream names to DeliveryStreamDescription - delivery_streams: Dict[str, DeliveryStreamDescription] = LocalAttribute(default=dict) + delivery_streams: dict[str, DeliveryStreamDescription] = LocalAttribute(default=dict) # static tagging service instance TAGS = CrossRegionAttribute(default=TaggingService) diff --git a/localstack-core/localstack/services/firehose/provider.py b/localstack-core/localstack/services/firehose/provider.py index 18142ae80d88b..6c8bdc5a560af 100644 --- a/localstack-core/localstack/services/firehose/provider.py +++ b/localstack-core/localstack/services/firehose/provider.py @@ -8,7 +8,6 @@ import time import uuid from datetime import datetime -from typing import Dict, List from urllib.parse import urlparse import requests @@ -609,7 +608,7 @@ def _reencode_record(self, record: Record) -> Record: record["Data"] = base64.b64encode(record["Data"]) return record - def _reencode_records(self, records: List[Record]) -> List[Record]: + def _reencode_records(self, records: list[Record]) -> list[Record]: return [self._reencode_record(r) for r in records] def _process_records( @@ -617,7 +616,7 @@ def _process_records( account_id: str, region_name: str, fh_d_stream: str, - records: List[Record], + records: list[Record], ): """Process the given records from the underlying Kinesis stream""" return self._put_records(account_id, region_name, fh_d_stream, records) @@ -638,8 +637,8 @@ def _put_records( account_id: str, region_name: str, delivery_stream_name: str, - unprocessed_records: List[Record], - ) -> List[PutRecordBatchResponseEntry]: + unprocessed_records: list[Record], + ) -> list[PutRecordBatchResponseEntry]: """Put a list of records to the firehose stream - either directly from a PutRecord API call, or received from an underlying Kinesis stream (if 'KinesisStreamAsSource' is configured)""" store = self.get_store(account_id, region_name) @@ -786,7 +785,7 @@ def _put_to_search_db( LOG.exception("Unable to put record to stream %s.", delivery_stream_name) raise e - def _add_missing_record_attributes(self, records: List[Dict]) -> None: + def _add_missing_record_attributes(self, records: list[dict]) -> None: def _get_entry(obj, key): return obj.get(key) or obj.get(first_char_to_lower(key)) @@ -806,7 +805,7 @@ def _get_entry(obj, key): "subsequenceNumber": "", } - def _preprocess_records(self, processor: Dict, records: List[Record]) -> List[Dict]: + def _preprocess_records(self, processor: dict, records: list[Record]) -> list[dict]: """Preprocess the list of records by calling the given processor (e.g., Lamnda function).""" proc_type = processor.get("Type") parameters = processor.get("Parameters", []) @@ -838,7 +837,7 @@ def _preprocess_records(self, processor: Dict, records: List[Record]) -> List[Di def _put_records_to_s3_bucket( self, stream_name: str, - records: List[Dict], + records: list[dict], s3_destination_description: S3DestinationDescription, ): bucket = s3_bucket_name(s3_destination_description["BucketARN"]) @@ -884,7 +883,7 @@ def _get_s3_object_path(self, stream_name, prefix, file_extension): def _put_to_redshift( self, - records: List[Dict], + records: list[dict], redshift_destination_description: RedshiftDestinationDescription, ): jdbcurl = redshift_destination_description.get("ClusterJDBCURL") @@ -940,13 +939,13 @@ def _get_region_from_jdbc_url(self, jdbc_url: str) -> str | None: LOG.debug("Cannot extract region from JDBC url '%s'", jdbc_url) return None - def _decode_record(self, record: Dict) -> Dict: + def _decode_record(self, record: dict) -> dict: data = base64.b64decode(record.get("Data") or record.get("data")) data = to_str(data) data = json.loads(data) return data - def _prepare_records_for_redshift(self, record: Dict) -> List[Dict]: + def _prepare_records_for_redshift(self, record: dict) -> list[dict]: data = self._decode_record(record) parameters = [] @@ -963,7 +962,7 @@ def _prepare_records_for_redshift(self, record: Dict) -> List[Dict]: return parameters - def _extract_columns(self, record: Dict) -> str: + def _extract_columns(self, record: dict) -> str: data = self._decode_record(record) placeholders = [f":{key}" for key in data] placeholder_str = ", ".join(placeholders) diff --git a/localstack-core/localstack/services/iam/iam_patches.py b/localstack-core/localstack/services/iam/iam_patches.py index bec31419c3c8f..65e7df8c20a8b 100644 --- a/localstack-core/localstack/services/iam/iam_patches.py +++ b/localstack-core/localstack/services/iam/iam_patches.py @@ -1,5 +1,4 @@ import threading -from typing import Dict, List, Optional from moto.iam.models import ( AccessKey, @@ -106,11 +105,11 @@ def iam_backend_create_role( role_name: str, assume_role_policy_document: str, path: str, - permissions_boundary: Optional[str], + permissions_boundary: str | None, description: str, - tags: List[Dict[str, str]], - max_session_duration: Optional[str], - linked_service: Optional[str] = None, + tags: list[dict[str, str]], + max_session_duration: str | None, + linked_service: str | None = None, ): role = fn( self, @@ -143,7 +142,7 @@ def inline_policy_unapply_policy(fn, self, backend): def access_key__init__( fn, self, - user_name: Optional[str], + user_name: str | None, prefix: str, account_id: str, status: str = "Active", diff --git a/localstack-core/localstack/services/iam/provider.py b/localstack-core/localstack/services/iam/provider.py index d5e1af505867d..9f90f37d3d7b7 100644 --- a/localstack-core/localstack/services/iam/provider.py +++ b/localstack-core/localstack/services/iam/provider.py @@ -6,7 +6,7 @@ import string import uuid from datetime import datetime -from typing import Any, Dict, List, TypeVar +from typing import Any, TypeVar from urllib.parse import quote from moto.iam.models import ( @@ -183,7 +183,7 @@ def create_role( @staticmethod def build_evaluation_result( - action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: List[Dict] + action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: list[dict] ) -> EvaluationResult: eval_res = EvaluationResult() eval_res["EvalActionName"] = action_name @@ -249,7 +249,7 @@ def delete_policy(self, context: RequestContext, policy_arn: arnType, **kwargs) if backend.managed_policies.get(policy_arn): backend.managed_policies.pop(policy_arn, None) else: - raise NoSuchEntityException("Policy {0} was not found.".format(policy_arn)) + raise NoSuchEntityException(f"Policy {policy_arn} was not found.") def detach_role_policy( self, context: RequestContext, role_name: roleNameType, policy_arn: arnType, **kwargs @@ -260,7 +260,7 @@ def detach_role_policy( policy = role.managed_policies[policy_arn] policy.detach_from(role) except KeyError: - raise NoSuchEntityException("Policy {0} was not found.".format(policy_arn)) + raise NoSuchEntityException(f"Policy {policy_arn} was not found.") @staticmethod def moto_role_to_role_type(moto_role: MotoRole) -> Role: diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py index a945e5af67a47..d1833e44b3d7f 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,11 +14,11 @@ class IAMAccessKeyProperties(TypedDict): - UserName: Optional[str] - Id: Optional[str] - SecretAccessKey: Optional[str] - Serial: Optional[int] - Status: Optional[str] + UserName: str | None + Id: str | None + SecretAccessKey: str | None + Serial: int | None + Status: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py index a54ee6f94b3db..690bc331be4d6 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_accesskey_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMAccessKeyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::AccessKey" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_accesskey import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py index 69c2b15ab1bfe..54b4918e5e1e0 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,17 +15,17 @@ class IAMGroupProperties(TypedDict): - Arn: Optional[str] - GroupName: Optional[str] - Id: Optional[str] - ManagedPolicyArns: Optional[list[str]] - Path: Optional[str] - Policies: Optional[list[Policy]] + Arn: str | None + GroupName: str | None + Id: str | None + ManagedPolicyArns: list[str] | None + Path: str | None + Policies: list[Policy] | None class Policy(TypedDict): - PolicyDocument: Optional[dict] - PolicyName: Optional[str] + PolicyDocument: dict | None + PolicyName: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py index 24af55af719b1..abb34b8751d2c 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_group_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::Group" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_group import IAMGroupProvider diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py index b65f5f079d0ff..b322af1c5c20e 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class IAMInstanceProfileProperties(TypedDict): - Roles: Optional[list[str]] - Arn: Optional[str] - InstanceProfileName: Optional[str] - Path: Optional[str] + Roles: list[str] | None + Arn: str | None + InstanceProfileName: str | None + Path: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py index 875b729a55323..f6ffb39efc944 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_instanceprofile_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMInstanceProfileProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::InstanceProfile" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_instanceprofile import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py index 0bca0e5a02169..b85f0997e79ed 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,14 +15,14 @@ class IAMManagedPolicyProperties(TypedDict): - PolicyDocument: Optional[dict] - Description: Optional[str] - Groups: Optional[list[str]] - Id: Optional[str] - ManagedPolicyName: Optional[str] - Path: Optional[str] - Roles: Optional[list[str]] - Users: Optional[list[str]] + PolicyDocument: dict | None + Description: str | None + Groups: list[str] | None + Id: str | None + ManagedPolicyName: str | None + Path: str | None + Roles: list[str] | None + Users: list[str] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py index d33ce61ef26b5..c47e80f66cd7e 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_managedpolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMManagedPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::ManagedPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_managedpolicy import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py index 97fdb19341b57..9273bb31d9597 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy.py @@ -5,7 +5,7 @@ import random import string from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,12 +17,12 @@ class IAMPolicyProperties(TypedDict): - PolicyDocument: Optional[dict] - PolicyName: Optional[str] - Groups: Optional[list[str]] - Id: Optional[str] - Roles: Optional[list[str]] - Users: Optional[list[str]] + PolicyDocument: dict | None + PolicyName: str | None + Groups: list[str] | None + Id: str | None + Roles: list[str] | None + Users: list[str] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py index a3fdd7e9c9dc3..43fd7a554b3f8 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_policy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::Policy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_policy import IAMPolicyProvider diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py index f3687337e332d..226cb4f68a8e0 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,27 +16,27 @@ class IAMRoleProperties(TypedDict): - AssumeRolePolicyDocument: Optional[dict | str] - Arn: Optional[str] - Description: Optional[str] - ManagedPolicyArns: Optional[list[str]] - MaxSessionDuration: Optional[int] - Path: Optional[str] - PermissionsBoundary: Optional[str] - Policies: Optional[list[Policy]] - RoleId: Optional[str] - RoleName: Optional[str] - Tags: Optional[list[Tag]] + AssumeRolePolicyDocument: dict | str | None + Arn: str | None + Description: str | None + ManagedPolicyArns: list[str] | None + MaxSessionDuration: int | None + Path: str | None + PermissionsBoundary: str | None + Policies: list[Policy] | None + RoleId: str | None + RoleName: str | None + Tags: list[Tag] | None class Policy(TypedDict): - PolicyDocument: Optional[str | dict] - PolicyName: Optional[str] + PolicyDocument: str | dict | None + PolicyName: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py index d6c7059f611eb..bc1b98e625923 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_role_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMRoleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::Role" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_role import IAMRoleProvider diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py index 233f9554efcc0..9778529826ad5 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class IAMServerCertificateProperties(TypedDict): - Arn: Optional[str] - CertificateBody: Optional[str] - CertificateChain: Optional[str] - Path: Optional[str] - PrivateKey: Optional[str] - ServerCertificateName: Optional[str] - Tags: Optional[list[Tag]] + Arn: str | None + CertificateBody: str | None + CertificateChain: str | None + Path: str | None + PrivateKey: str | None + ServerCertificateName: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py index 13723bd73ce2b..a90f432110b8d 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servercertificate_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMServerCertificateProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::ServerCertificate" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_servercertificate import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py index 2437966df10e7..441e6a0545aed 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class IAMServiceLinkedRoleProperties(TypedDict): - AWSServiceName: Optional[str] - CustomSuffix: Optional[str] - Description: Optional[str] - Id: Optional[str] + AWSServiceName: str | None + CustomSuffix: str | None + Description: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py index e81cc105f85c1..98c74c9912779 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_servicelinkedrole_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMServiceLinkedRoleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::ServiceLinkedRole" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_servicelinkedrole import ( diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py index 8600522013b39..373c7442ef326 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,31 +14,31 @@ class IAMUserProperties(TypedDict): - Arn: Optional[str] - Groups: Optional[list[str]] - Id: Optional[str] - LoginProfile: Optional[LoginProfile] - ManagedPolicyArns: Optional[list[str]] - Path: Optional[str] - PermissionsBoundary: Optional[str] - Policies: Optional[list[Policy]] - Tags: Optional[list[Tag]] - UserName: Optional[str] + Arn: str | None + Groups: list[str] | None + Id: str | None + LoginProfile: LoginProfile | None + ManagedPolicyArns: list[str] | None + Path: str | None + PermissionsBoundary: str | None + Policies: list[Policy] | None + Tags: list[Tag] | None + UserName: str | None class Policy(TypedDict): - PolicyDocument: Optional[dict] - PolicyName: Optional[str] + PolicyDocument: dict | None + PolicyName: str | None class LoginProfile(TypedDict): - Password: Optional[str] - PasswordResetRequired: Optional[bool] + Password: str | None + PasswordResetRequired: bool | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py index 60acd8fc1493c..abb2910dbe3e7 100644 --- a/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py +++ b/localstack-core/localstack/services/iam/resource_providers/aws_iam_user_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class IAMUserProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::IAM::User" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.iam.resource_providers.aws_iam_user import IAMUserProvider diff --git a/localstack-core/localstack/services/kinesis/kinesis_mock_server.py b/localstack-core/localstack/services/kinesis/kinesis_mock_server.py index b9ce394e1415d..92ef250bec6e8 100644 --- a/localstack-core/localstack/services/kinesis/kinesis_mock_server.py +++ b/localstack-core/localstack/services/kinesis/kinesis_mock_server.py @@ -3,7 +3,6 @@ import threading from abc import abstractmethod from pathlib import Path -from typing import Dict, List, Optional, Tuple from localstack import config from localstack.services.kinesis.packages import ( @@ -31,7 +30,7 @@ def __init__( account_id: str, host: str = "localhost", log_level: str = "INFO", - data_dir: Optional[str] = None, + data_dir: str | None = None, ) -> None: self._account_id = account_id self._latency = latency @@ -57,7 +56,7 @@ def do_start_thread(self) -> FuncThread: return t @property - def _environment_variables(self) -> Dict: + def _environment_variables(self) -> dict: env_vars = { "KINESIS_MOCK_PLAIN_PORT": self.port, # Each kinesis-mock instance listens to two ports - secure and insecure. @@ -94,7 +93,7 @@ def _environment_variables(self) -> Dict: return env_vars @abstractmethod - def _create_shell_command(self) -> Tuple[List, Dict]: + def _create_shell_command(self) -> tuple[list, dict]: """ Helper method for creating kinesis mock invocation command :return: returns a tuple containing the command list and a dictionary with the environment variables @@ -106,12 +105,12 @@ def _log_listener(self, line, **_kwargs): class KinesisMockScalaServer(KinesisMockServer): - def _create_shell_command(self) -> Tuple[List, Dict]: + def _create_shell_command(self) -> tuple[list, dict]: cmd = ["java", "-jar", *self._get_java_vm_options(), str(self._exe_path)] return cmd, self._environment_variables @property - def _environment_variables(self) -> Dict: + def _environment_variables(self) -> dict: default_env_vars = super()._environment_variables kinesis_mock_installer = kinesismock_scala_package.get_installer() return { @@ -130,7 +129,7 @@ def _get_java_vm_options(self) -> list[str]: class KinesisMockNodeServer(KinesisMockServer): @property - def _environment_variables(self) -> Dict: + def _environment_variables(self) -> dict: node_env_vars = { # Use the `server.json` packaged next to the main.js "KINESIS_MOCK_CERT_PATH": str((self._exe_path.parent / "server.json").absolute()), @@ -139,7 +138,7 @@ def _environment_variables(self) -> Dict: default_env_vars = super()._environment_variables return {**node_env_vars, **default_env_vars} - def _create_shell_command(self) -> Tuple[List, Dict]: + def _create_shell_command(self) -> tuple[list, dict]: cmd = ["node", self._exe_path] return cmd, self._environment_variables diff --git a/localstack-core/localstack/services/kinesis/models.py b/localstack-core/localstack/services/kinesis/models.py index 3247ac060fbb0..20207a702844a 100644 --- a/localstack-core/localstack/services/kinesis/models.py +++ b/localstack-core/localstack/services/kinesis/models.py @@ -1,5 +1,4 @@ from collections import defaultdict -from typing import Dict, List, Set from localstack.aws.api.kinesis import ConsumerDescription, MetricsName, StreamName from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute @@ -7,10 +6,10 @@ class KinesisStore(BaseStore): # list of stream consumer details - stream_consumers: List[ConsumerDescription] = LocalAttribute(default=list) + stream_consumers: list[ConsumerDescription] = LocalAttribute(default=list) # maps stream name to list of enhanced monitoring metrics - enhanced_metrics: Dict[StreamName, Set[MetricsName]] = LocalAttribute( + enhanced_metrics: dict[StreamName, set[MetricsName]] = LocalAttribute( default=lambda: defaultdict(set) ) diff --git a/localstack-core/localstack/services/kinesis/packages.py b/localstack-core/localstack/services/kinesis/packages.py index c7b07eb7b9dd7..1d0615d3fc109 100644 --- a/localstack-core/localstack/services/kinesis/packages.py +++ b/localstack-core/localstack/services/kinesis/packages.py @@ -1,7 +1,7 @@ import os from enum import StrEnum from functools import lru_cache -from typing import Any, List +from typing import Any from localstack.packages import InstallTarget, Package from localstack.packages.core import GitHubReleaseInstaller, NodePackageInstaller @@ -58,7 +58,7 @@ def __init__( def _get_installer(self, version: str) -> KinesisMockScalaPackageInstaller: return KinesisMockScalaPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [_KINESIS_MOCK_VERSION] # Only supported on v0.4.12+ @@ -73,7 +73,7 @@ def __init__( def _get_installer(self, version: str) -> KinesisMockNodePackageInstaller: return KinesisMockNodePackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [_KINESIS_MOCK_VERSION] diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py index 28d231d666484..5d805de0bba97 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,27 +14,27 @@ class KinesisStreamProperties(TypedDict): - Arn: Optional[str] - Name: Optional[str] - RetentionPeriodHours: Optional[int] - ShardCount: Optional[int] - StreamEncryption: Optional[StreamEncryption] - StreamModeDetails: Optional[StreamModeDetails] - Tags: Optional[list[Tag]] + Arn: str | None + Name: str | None + RetentionPeriodHours: int | None + ShardCount: int | None + StreamEncryption: StreamEncryption | None + StreamModeDetails: StreamModeDetails | None + Tags: list[Tag] | None class StreamModeDetails(TypedDict): - StreamMode: Optional[str] + StreamMode: str | None class StreamEncryption(TypedDict): - EncryptionType: Optional[str] - KeyId: Optional[str] + EncryptionType: str | None + KeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py index d7e834e7bb0bf..9f6a783801fd2 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_stream_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KinesisStreamProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Kinesis::Stream" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kinesis.resource_providers.aws_kinesis_stream import ( diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py index e9ba78fc42d0e..03d4bf6d41dfc 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,12 +14,12 @@ class KinesisStreamConsumerProperties(TypedDict): - ConsumerName: Optional[str] - StreamARN: Optional[str] - ConsumerARN: Optional[str] - ConsumerCreationTimestamp: Optional[str] - ConsumerStatus: Optional[str] - Id: Optional[str] + ConsumerName: str | None + StreamARN: str | None + ConsumerARN: str | None + ConsumerCreationTimestamp: str | None + ConsumerStatus: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py index b1f2cab38423d..9710d11adbb49 100644 --- a/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py +++ b/localstack-core/localstack/services/kinesis/resource_providers/aws_kinesis_streamconsumer_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KinesisStreamConsumerProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Kinesis::StreamConsumer" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kinesis.resource_providers.aws_kinesis_streamconsumer import ( diff --git a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py index 6764a783667f0..0af6cfaee3069 100644 --- a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py +++ b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,328 +14,328 @@ class KinesisFirehoseDeliveryStreamProperties(TypedDict): - AmazonOpenSearchServerlessDestinationConfiguration: Optional[ - AmazonOpenSearchServerlessDestinationConfiguration - ] - AmazonopensearchserviceDestinationConfiguration: Optional[ - AmazonopensearchserviceDestinationConfiguration - ] - Arn: Optional[str] - DeliveryStreamEncryptionConfigurationInput: Optional[DeliveryStreamEncryptionConfigurationInput] - DeliveryStreamName: Optional[str] - DeliveryStreamType: Optional[str] - ElasticsearchDestinationConfiguration: Optional[ElasticsearchDestinationConfiguration] - ExtendedS3DestinationConfiguration: Optional[ExtendedS3DestinationConfiguration] - HttpEndpointDestinationConfiguration: Optional[HttpEndpointDestinationConfiguration] - KinesisStreamSourceConfiguration: Optional[KinesisStreamSourceConfiguration] - RedshiftDestinationConfiguration: Optional[RedshiftDestinationConfiguration] - S3DestinationConfiguration: Optional[S3DestinationConfiguration] - SplunkDestinationConfiguration: Optional[SplunkDestinationConfiguration] - Tags: Optional[list[Tag]] + AmazonOpenSearchServerlessDestinationConfiguration: ( + AmazonOpenSearchServerlessDestinationConfiguration | None + ) + AmazonopensearchserviceDestinationConfiguration: ( + AmazonopensearchserviceDestinationConfiguration | None + ) + Arn: str | None + DeliveryStreamEncryptionConfigurationInput: DeliveryStreamEncryptionConfigurationInput | None + DeliveryStreamName: str | None + DeliveryStreamType: str | None + ElasticsearchDestinationConfiguration: ElasticsearchDestinationConfiguration | None + ExtendedS3DestinationConfiguration: ExtendedS3DestinationConfiguration | None + HttpEndpointDestinationConfiguration: HttpEndpointDestinationConfiguration | None + KinesisStreamSourceConfiguration: KinesisStreamSourceConfiguration | None + RedshiftDestinationConfiguration: RedshiftDestinationConfiguration | None + S3DestinationConfiguration: S3DestinationConfiguration | None + SplunkDestinationConfiguration: SplunkDestinationConfiguration | None + Tags: list[Tag] | None class DeliveryStreamEncryptionConfigurationInput(TypedDict): - KeyType: Optional[str] - KeyARN: Optional[str] + KeyType: str | None + KeyARN: str | None class ElasticsearchBufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class CloudWatchLoggingOptions(TypedDict): - Enabled: Optional[bool] - LogGroupName: Optional[str] - LogStreamName: Optional[str] + Enabled: bool | None + LogGroupName: str | None + LogStreamName: str | None class ProcessorParameter(TypedDict): - ParameterName: Optional[str] - ParameterValue: Optional[str] + ParameterName: str | None + ParameterValue: str | None class Processor(TypedDict): - Type: Optional[str] - Parameters: Optional[list[ProcessorParameter]] + Type: str | None + Parameters: list[ProcessorParameter] | None class ProcessingConfiguration(TypedDict): - Enabled: Optional[bool] - Processors: Optional[list[Processor]] + Enabled: bool | None + Processors: list[Processor] | None class ElasticsearchRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class BufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class KMSEncryptionConfig(TypedDict): - AWSKMSKeyARN: Optional[str] + AWSKMSKeyARN: str | None class EncryptionConfiguration(TypedDict): - KMSEncryptionConfig: Optional[KMSEncryptionConfig] - NoEncryptionConfig: Optional[str] + KMSEncryptionConfig: KMSEncryptionConfig | None + NoEncryptionConfig: str | None class S3DestinationConfiguration(TypedDict): - BucketARN: Optional[str] - RoleARN: Optional[str] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - CompressionFormat: Optional[str] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ErrorOutputPrefix: Optional[str] - Prefix: Optional[str] + BucketARN: str | None + RoleARN: str | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + CompressionFormat: str | None + EncryptionConfiguration: EncryptionConfiguration | None + ErrorOutputPrefix: str | None + Prefix: str | None class VpcConfiguration(TypedDict): - RoleARN: Optional[str] - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + RoleARN: str | None + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class DocumentIdOptions(TypedDict): - DefaultDocumentIdFormat: Optional[str] + DefaultDocumentIdFormat: str | None class ElasticsearchDestinationConfiguration(TypedDict): - IndexName: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[ElasticsearchBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ClusterEndpoint: Optional[str] - DocumentIdOptions: Optional[DocumentIdOptions] - DomainARN: Optional[str] - IndexRotationPeriod: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[ElasticsearchRetryOptions] - S3BackupMode: Optional[str] - TypeName: Optional[str] - VpcConfiguration: Optional[VpcConfiguration] + IndexName: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: ElasticsearchBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ClusterEndpoint: str | None + DocumentIdOptions: DocumentIdOptions | None + DomainARN: str | None + IndexRotationPeriod: str | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: ElasticsearchRetryOptions | None + S3BackupMode: str | None + TypeName: str | None + VpcConfiguration: VpcConfiguration | None class AmazonopensearchserviceBufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class AmazonopensearchserviceRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class AmazonopensearchserviceDestinationConfiguration(TypedDict): - IndexName: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[AmazonopensearchserviceBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ClusterEndpoint: Optional[str] - DocumentIdOptions: Optional[DocumentIdOptions] - DomainARN: Optional[str] - IndexRotationPeriod: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[AmazonopensearchserviceRetryOptions] - S3BackupMode: Optional[str] - TypeName: Optional[str] - VpcConfiguration: Optional[VpcConfiguration] + IndexName: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: AmazonopensearchserviceBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ClusterEndpoint: str | None + DocumentIdOptions: DocumentIdOptions | None + DomainARN: str | None + IndexRotationPeriod: str | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: AmazonopensearchserviceRetryOptions | None + S3BackupMode: str | None + TypeName: str | None + VpcConfiguration: VpcConfiguration | None class AmazonOpenSearchServerlessBufferingHints(TypedDict): - IntervalInSeconds: Optional[int] - SizeInMBs: Optional[int] + IntervalInSeconds: int | None + SizeInMBs: int | None class AmazonOpenSearchServerlessRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class AmazonOpenSearchServerlessDestinationConfiguration(TypedDict): - IndexName: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[AmazonOpenSearchServerlessBufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - CollectionEndpoint: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[AmazonOpenSearchServerlessRetryOptions] - S3BackupMode: Optional[str] - VpcConfiguration: Optional[VpcConfiguration] + IndexName: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: AmazonOpenSearchServerlessBufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + CollectionEndpoint: str | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: AmazonOpenSearchServerlessRetryOptions | None + S3BackupMode: str | None + VpcConfiguration: VpcConfiguration | None class HiveJsonSerDe(TypedDict): - TimestampFormats: Optional[list[str]] + TimestampFormats: list[str] | None class OpenXJsonSerDe(TypedDict): - CaseInsensitive: Optional[bool] - ColumnToJsonKeyMappings: Optional[dict] - ConvertDotsInJsonKeysToUnderscores: Optional[bool] + CaseInsensitive: bool | None + ColumnToJsonKeyMappings: dict | None + ConvertDotsInJsonKeysToUnderscores: bool | None class Deserializer(TypedDict): - HiveJsonSerDe: Optional[HiveJsonSerDe] - OpenXJsonSerDe: Optional[OpenXJsonSerDe] + HiveJsonSerDe: HiveJsonSerDe | None + OpenXJsonSerDe: OpenXJsonSerDe | None class InputFormatConfiguration(TypedDict): - Deserializer: Optional[Deserializer] + Deserializer: Deserializer | None class OrcSerDe(TypedDict): - BlockSizeBytes: Optional[int] - BloomFilterColumns: Optional[list[str]] - BloomFilterFalsePositiveProbability: Optional[float] - Compression: Optional[str] - DictionaryKeyThreshold: Optional[float] - EnablePadding: Optional[bool] - FormatVersion: Optional[str] - PaddingTolerance: Optional[float] - RowIndexStride: Optional[int] - StripeSizeBytes: Optional[int] + BlockSizeBytes: int | None + BloomFilterColumns: list[str] | None + BloomFilterFalsePositiveProbability: float | None + Compression: str | None + DictionaryKeyThreshold: float | None + EnablePadding: bool | None + FormatVersion: str | None + PaddingTolerance: float | None + RowIndexStride: int | None + StripeSizeBytes: int | None class ParquetSerDe(TypedDict): - BlockSizeBytes: Optional[int] - Compression: Optional[str] - EnableDictionaryCompression: Optional[bool] - MaxPaddingBytes: Optional[int] - PageSizeBytes: Optional[int] - WriterVersion: Optional[str] + BlockSizeBytes: int | None + Compression: str | None + EnableDictionaryCompression: bool | None + MaxPaddingBytes: int | None + PageSizeBytes: int | None + WriterVersion: str | None class Serializer(TypedDict): - OrcSerDe: Optional[OrcSerDe] - ParquetSerDe: Optional[ParquetSerDe] + OrcSerDe: OrcSerDe | None + ParquetSerDe: ParquetSerDe | None class OutputFormatConfiguration(TypedDict): - Serializer: Optional[Serializer] + Serializer: Serializer | None class SchemaConfiguration(TypedDict): - CatalogId: Optional[str] - DatabaseName: Optional[str] - Region: Optional[str] - RoleARN: Optional[str] - TableName: Optional[str] - VersionId: Optional[str] + CatalogId: str | None + DatabaseName: str | None + Region: str | None + RoleARN: str | None + TableName: str | None + VersionId: str | None class DataFormatConversionConfiguration(TypedDict): - Enabled: Optional[bool] - InputFormatConfiguration: Optional[InputFormatConfiguration] - OutputFormatConfiguration: Optional[OutputFormatConfiguration] - SchemaConfiguration: Optional[SchemaConfiguration] + Enabled: bool | None + InputFormatConfiguration: InputFormatConfiguration | None + OutputFormatConfiguration: OutputFormatConfiguration | None + SchemaConfiguration: SchemaConfiguration | None class RetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class DynamicPartitioningConfiguration(TypedDict): - Enabled: Optional[bool] - RetryOptions: Optional[RetryOptions] + Enabled: bool | None + RetryOptions: RetryOptions | None class ExtendedS3DestinationConfiguration(TypedDict): - BucketARN: Optional[str] - RoleARN: Optional[str] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - CompressionFormat: Optional[str] - DataFormatConversionConfiguration: Optional[DataFormatConversionConfiguration] - DynamicPartitioningConfiguration: Optional[DynamicPartitioningConfiguration] - EncryptionConfiguration: Optional[EncryptionConfiguration] - ErrorOutputPrefix: Optional[str] - Prefix: Optional[str] - ProcessingConfiguration: Optional[ProcessingConfiguration] - S3BackupConfiguration: Optional[S3DestinationConfiguration] - S3BackupMode: Optional[str] + BucketARN: str | None + RoleARN: str | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + CompressionFormat: str | None + DataFormatConversionConfiguration: DataFormatConversionConfiguration | None + DynamicPartitioningConfiguration: DynamicPartitioningConfiguration | None + EncryptionConfiguration: EncryptionConfiguration | None + ErrorOutputPrefix: str | None + Prefix: str | None + ProcessingConfiguration: ProcessingConfiguration | None + S3BackupConfiguration: S3DestinationConfiguration | None + S3BackupMode: str | None class KinesisStreamSourceConfiguration(TypedDict): - KinesisStreamARN: Optional[str] - RoleARN: Optional[str] + KinesisStreamARN: str | None + RoleARN: str | None class CopyCommand(TypedDict): - DataTableName: Optional[str] - CopyOptions: Optional[str] - DataTableColumns: Optional[str] + DataTableName: str | None + CopyOptions: str | None + DataTableColumns: str | None class RedshiftRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class RedshiftDestinationConfiguration(TypedDict): - ClusterJDBCURL: Optional[str] - CopyCommand: Optional[CopyCommand] - Password: Optional[str] - RoleARN: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - Username: Optional[str] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[RedshiftRetryOptions] - S3BackupConfiguration: Optional[S3DestinationConfiguration] - S3BackupMode: Optional[str] + ClusterJDBCURL: str | None + CopyCommand: CopyCommand | None + Password: str | None + RoleARN: str | None + S3Configuration: S3DestinationConfiguration | None + Username: str | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: RedshiftRetryOptions | None + S3BackupConfiguration: S3DestinationConfiguration | None + S3BackupMode: str | None class SplunkRetryOptions(TypedDict): - DurationInSeconds: Optional[int] + DurationInSeconds: int | None class SplunkDestinationConfiguration(TypedDict): - HECEndpoint: Optional[str] - HECEndpointType: Optional[str] - HECToken: Optional[str] - S3Configuration: Optional[S3DestinationConfiguration] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - HECAcknowledgmentTimeoutInSeconds: Optional[int] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RetryOptions: Optional[SplunkRetryOptions] - S3BackupMode: Optional[str] + HECEndpoint: str | None + HECEndpointType: str | None + HECToken: str | None + S3Configuration: S3DestinationConfiguration | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + HECAcknowledgmentTimeoutInSeconds: int | None + ProcessingConfiguration: ProcessingConfiguration | None + RetryOptions: SplunkRetryOptions | None + S3BackupMode: str | None class HttpEndpointConfiguration(TypedDict): - Url: Optional[str] - AccessKey: Optional[str] - Name: Optional[str] + Url: str | None + AccessKey: str | None + Name: str | None class HttpEndpointCommonAttribute(TypedDict): - AttributeName: Optional[str] - AttributeValue: Optional[str] + AttributeName: str | None + AttributeValue: str | None class HttpEndpointRequestConfiguration(TypedDict): - CommonAttributes: Optional[list[HttpEndpointCommonAttribute]] - ContentEncoding: Optional[str] + CommonAttributes: list[HttpEndpointCommonAttribute] | None + ContentEncoding: str | None class HttpEndpointDestinationConfiguration(TypedDict): - EndpointConfiguration: Optional[HttpEndpointConfiguration] - S3Configuration: Optional[S3DestinationConfiguration] - BufferingHints: Optional[BufferingHints] - CloudWatchLoggingOptions: Optional[CloudWatchLoggingOptions] - ProcessingConfiguration: Optional[ProcessingConfiguration] - RequestConfiguration: Optional[HttpEndpointRequestConfiguration] - RetryOptions: Optional[RetryOptions] - RoleARN: Optional[str] - S3BackupMode: Optional[str] + EndpointConfiguration: HttpEndpointConfiguration | None + S3Configuration: S3DestinationConfiguration | None + BufferingHints: BufferingHints | None + CloudWatchLoggingOptions: CloudWatchLoggingOptions | None + ProcessingConfiguration: ProcessingConfiguration | None + RequestConfiguration: HttpEndpointRequestConfiguration | None + RetryOptions: RetryOptions | None + RoleARN: str | None + S3BackupMode: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py index 772007e6ce18d..b497397f1e8ec 100644 --- a/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py +++ b/localstack-core/localstack/services/kinesisfirehose/resource_providers/aws_kinesisfirehose_deliverystream_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KinesisFirehoseDeliveryStreamProviderPlugin(CloudFormationResourceProvider name = "AWS::KinesisFirehose::DeliveryStream" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kinesisfirehose.resource_providers.aws_kinesisfirehose_deliverystream import ( diff --git a/localstack-core/localstack/services/kms/models.py b/localstack-core/localstack/services/kms/models.py index 3479e309d4903..f28d2995edd7d 100644 --- a/localstack-core/localstack/services/kms/models.py +++ b/localstack-core/localstack/services/kms/models.py @@ -10,7 +10,6 @@ import uuid from collections import namedtuple from dataclasses import dataclass -from typing import Dict, Optional, Tuple from cryptography.exceptions import InvalidSignature, InvalidTag, UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend @@ -91,9 +90,7 @@ # Moto uses IV_LEN of 12, as it is fine for GCM encryption mode, but we use CBC, so have to set it to 16. IV_LEN = 16 TAG_LEN = 16 -CIPHERTEXT_HEADER_FORMAT = ">{key_id_len}s{iv_len}s{tag_len}s".format( - key_id_len=KEY_ID_LEN, iv_len=IV_LEN, tag_len=TAG_LEN -) +CIPHERTEXT_HEADER_FORMAT = f">{KEY_ID_LEN}s{IV_LEN}s{TAG_LEN}s" HEADER_LEN = KEY_ID_LEN + IV_LEN + TAG_LEN Ciphertext = namedtuple("Ciphertext", ("key_id", "iv", "ciphertext", "tag")) @@ -138,7 +135,7 @@ def deserialize_ciphertext_blob(ciphertext_blob: bytes) -> Ciphertext: return Ciphertext(key_id=key_id.decode("utf-8"), iv=iv, ciphertext=ciphertext, tag=tag) -def _serialize_encryption_context(encryption_context: Optional[EncryptionContextType]) -> bytes: +def _serialize_encryption_context(encryption_context: EncryptionContextType | None) -> bytes: if encryption_context: aad = io.BytesIO() for key, value in sorted(encryption_context.items(), key=lambda x: x[0]): @@ -173,8 +170,8 @@ class KmsCryptoKey: by AWS and is not used by AWS. """ - public_key: Optional[bytes] - private_key: Optional[bytes] + public_key: bytes | None + private_key: bytes | None key_material: bytes key_spec: str @@ -217,7 +214,7 @@ def raise_validation(): raise UnsupportedOperationException(f"KeySpec {key_spec} is not supported") - def __init__(self, key_spec: str, key_material: Optional[bytes] = None): + def __init__(self, key_spec: str, key_material: bytes | None = None): self.private_key = None self.public_key = None # Technically, key_material, being a symmetric encryption key, is only relevant for @@ -251,7 +248,13 @@ def __init__(self, key_spec: str, key_material: Optional[bytes] = None): self._serialize_key(key) def load_key_material(self, material: bytes): - if self.key_spec == "SYMMETRIC_DEFAULT": + if self.key_spec in [ + KeySpec.SYMMETRIC_DEFAULT, + KeySpec.HMAC_224, + KeySpec.HMAC_256, + KeySpec.HMAC_384, + KeySpec.HMAC_512, + ]: self.key_material = material else: key = crypto_serialization.load_der_private_key(material, password=None) @@ -280,7 +283,7 @@ def key(self) -> RSAPrivateKey | EllipticCurvePrivateKey: class KmsKey: metadata: KeyMetadata crypto_key: KmsCryptoKey - tags: Dict[str, str] + tags: dict[str, str] policy: str is_key_rotation_enabled: bool rotation_period_in_days: int @@ -752,7 +755,7 @@ class KmsGrant: # keys. But, based on our understanding of AWS documentation for CreateGrant, ListGrants operations etc, # AWS has some set of fields for grants like it has for keys. So we are going to call them `metadata` here for # consistency. - metadata: Dict + metadata: dict # Tokens are not a part of metadata, as their use is more limited and specific than for the rest of the # metadata: https://docs.aws.amazon.com/kms/latest/developerguide/grant-manage.html#using-grant-token # Tokens are used to refer to a grant in a short period right after the grant gets created. Normally it might @@ -797,7 +800,7 @@ def __init__(self, create_grant_request: CreateGrantRequest, account_id: str, re class KmsAlias: # Like with grants (see comment for KmsGrant), there is no mention of some specific object modeling metadata # for KMS aliases. But there is data that is some metadata, so we model it in a way similar to KeyMetadata for keys. - metadata: Dict + metadata: dict def __init__( self, @@ -827,25 +830,25 @@ class KeyImportState: class KmsStore(BaseStore): # maps key ids to keys - keys: Dict[str, KmsKey] = LocalAttribute(default=dict) + keys: dict[str, KmsKey] = LocalAttribute(default=dict) # According to AWS documentation on grants https://docs.aws.amazon.com/kms/latest/APIReference/API_RetireGrant.html # "Cross-account use: Yes. You can retire a grant on a KMS key in a different AWS account." # maps grant ids to grants - grants: Dict[str, KmsGrant] = LocalAttribute(default=dict) + grants: dict[str, KmsGrant] = LocalAttribute(default=dict) # maps from (grant names (used for idempotency), key id) to grant ids - grant_names: Dict[Tuple[str, str], str] = LocalAttribute(default=dict) + grant_names: dict[tuple[str, str], str] = LocalAttribute(default=dict) # maps grant tokens to grant ids - grant_tokens: Dict[str, str] = LocalAttribute(default=dict) + grant_tokens: dict[str, str] = LocalAttribute(default=dict) # maps key alias names to aliases - aliases: Dict[str, KmsAlias] = LocalAttribute(default=dict) + aliases: dict[str, KmsAlias] = LocalAttribute(default=dict) # maps import tokens to import data - imports: Dict[str, KeyImportState] = LocalAttribute(default=dict) + imports: dict[str, KeyImportState] = LocalAttribute(default=dict) kms_stores = AccountRegionBundle("kms", KmsStore) diff --git a/localstack-core/localstack/services/kms/provider.py b/localstack-core/localstack/services/kms/provider.py index 3f3cd7383374d..9f9c8f6d32236 100644 --- a/localstack-core/localstack/services/kms/provider.py +++ b/localstack-core/localstack/services/kms/provider.py @@ -3,7 +3,6 @@ import datetime import logging import os -from typing import Dict, Tuple from cryptography.exceptions import InvalidTag from cryptography.hazmat.backends import default_backend @@ -359,7 +358,7 @@ def _get_kms_alias(account_id: str, region_name: str, alias_name_or_arn: str) -> return store.aliases.get(alias_name) @staticmethod - def _parse_key_id(key_id_or_arn: str, context: RequestContext) -> Tuple[str, str, str]: + def _parse_key_id(key_id_or_arn: str, context: RequestContext) -> tuple[str, str, str]: """ Return locator attributes (account ID, region_name, key ID) of a given KMS key. @@ -1540,7 +1539,7 @@ def _validate_plaintext_length(self, plaintext: bytes): "Member must have length less than or equal to 4096" ) - def _validate_grant_request(self, data: Dict): + def _validate_grant_request(self, data: dict): if "KeyId" not in data or "GranteePrincipal" not in data or "Operations" not in data: raise ValidationError("Grant ID, key ID and grantee principal must be specified") diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py index 81ecef65ca520..a039747e815eb 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,8 +14,8 @@ class KMSAliasProperties(TypedDict): - AliasName: Optional[str] - TargetKeyId: Optional[str] + AliasName: str | None + TargetKeyId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py index 172d4915576ce..4a5c0177d1fcb 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_alias_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KMSAliasProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::KMS::Alias" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kms.resource_providers.aws_kms_alias import KMSAliasProvider diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py index 6228292ed2953..cce09f36459a9 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,22 +15,22 @@ class KMSKeyProperties(TypedDict): - KeyPolicy: Optional[dict | str] - Arn: Optional[str] - Description: Optional[str] - EnableKeyRotation: Optional[bool] - Enabled: Optional[bool] - KeyId: Optional[str] - KeySpec: Optional[str] - KeyUsage: Optional[str] - MultiRegion: Optional[bool] - PendingWindowInDays: Optional[int] - Tags: Optional[list[Tag]] + KeyPolicy: dict | str | None + Arn: str | None + Description: str | None + EnableKeyRotation: bool | None + Enabled: bool | None + KeyId: str | None + KeySpec: str | None + KeyUsage: str | None + MultiRegion: bool | None + PendingWindowInDays: int | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py index a03c3c714af8c..13f2a28e7a517 100644 --- a/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py +++ b/localstack-core/localstack/services/kms/resource_providers/aws_kms_key_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class KMSKeyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::KMS::Key" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.kms.resource_providers.aws_kms_key import KMSKeyProvider diff --git a/localstack-core/localstack/services/kms/utils.py b/localstack-core/localstack/services/kms/utils.py index ae9ff4580caa1..550d9a31c23c7 100644 --- a/localstack-core/localstack/services/kms/utils.py +++ b/localstack-core/localstack/services/kms/utils.py @@ -1,5 +1,6 @@ import re -from typing import Callable, Tuple, TypeVar +from collections.abc import Callable +from typing import TypeVar from localstack.aws.api.kms import DryRunOperationException, Tag, TagException from localstack.services.kms.exceptions import ValidationException @@ -8,7 +9,7 @@ T = TypeVar("T") KMS_KEY_ARN_PATTERN = re.compile( - rf"{ARN_PARTITION_REGEX}:kms:(?P[^:]+):(?P\d{{12}}):key\/(?P[^:]+)$" + rf"{ARN_PARTITION_REGEX}:kms:(?P[^:]+):(?P\d{{12}}):((?=key/)key/|(?=alias/))(?P[^:]+)$" ) @@ -20,7 +21,7 @@ def get_hash_algorithm(signing_algorithm: str) -> str: return "_".join(signing_algorithm.rsplit(sep="_", maxsplit=-2)[-2:]) -def parse_key_arn(key_arn: str) -> Tuple[str, str, str]: +def parse_key_arn(key_arn: str) -> tuple[str, str, str]: """ Parse a valid KMS key arn into its constituents. diff --git a/localstack-core/localstack/services/lambda_/api_utils.py b/localstack-core/localstack/services/lambda_/api_utils.py index bc573c5e019f6..4e176d676e710 100644 --- a/localstack-core/localstack/services/lambda_/api_utils.py +++ b/localstack-core/localstack/services/lambda_/api_utils.py @@ -6,7 +6,7 @@ import random import re import string -from typing import TYPE_CHECKING, Any, Optional, Tuple +from typing import TYPE_CHECKING, Any from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api import lambda_ as api_spec @@ -192,7 +192,7 @@ def map_csc(model: "CodeSigningConfig") -> api_spec.CodeSigningConfig: ) -def get_config_for_url(store: "LambdaStore", url_id: str) -> "Optional[FunctionUrlConfig]": +def get_config_for_url(store: "LambdaStore", url_id: str) -> "FunctionUrlConfig | None": """ Get a config object when resolving a URL @@ -269,7 +269,7 @@ def function_locators_from_arn(arn: str) -> tuple[str | None, str | None, str | return None, None, None, None -def get_account_and_region(function_arn_or_name: str, context: RequestContext) -> Tuple[str, str]: +def get_account_and_region(function_arn_or_name: str, context: RequestContext) -> tuple[str, str]: """ Takes a full ARN, partial ARN or a name. Returns account ID and region from ARN if available, else falls back to context account ID and region. @@ -345,11 +345,11 @@ def build_statement( statement_id: str, action: str, principal: str, - source_arn: Optional[str] = None, - source_account: Optional[str] = None, - principal_org_id: Optional[str] = None, - event_source_token: Optional[str] = None, - auth_type: Optional[FunctionUrlAuthType] = None, + source_arn: str | None = None, + source_account: str | None = None, + principal_org_id: str | None = None, + event_source_token: str | None = None, + auth_type: FunctionUrlAuthType | None = None, ) -> dict[str, Any]: statement = { "Sid": statement_id, @@ -377,7 +377,7 @@ def build_statement( Type="User", ) - condition = dict() + condition = {} if auth_type: update = {"StringEquals": {"lambda:FunctionUrlAuthType": auth_type}} condition = merge_recursive(condition, update) @@ -425,7 +425,7 @@ def unqualified_lambda_arn(function_name: str, account: str, region: str): def qualified_lambda_arn( - function_name: str, qualifier: Optional[str], account: str, region: str + function_name: str, qualifier: str | None, account: str, region: str ) -> str: """ Generate a qualified lambda arn @@ -440,7 +440,7 @@ def qualified_lambda_arn( return f"{unqualified_lambda_arn(function_name=function_name, account=account, region=region)}:{qualifier}" -def lambda_arn(function_name: str, qualifier: Optional[str], account: str, region: str) -> str: +def lambda_arn(function_name: str, qualifier: str | None, account: str, region: str) -> str: """ Return the lambda arn for the given parameters, with a qualifier if supplied, without otherwise @@ -637,7 +637,7 @@ def map_alias_out(alias: "VersionAlias", function: "Function") -> AliasConfigura ) -def validate_and_set_batch_size(service: str, batch_size: Optional[int] = None) -> int: +def validate_and_set_batch_size(service: str, batch_size: int | None = None) -> int: min_batch_size = 1 BATCH_SIZE_RANGES = { @@ -690,7 +690,7 @@ def layer_version_arn(layer_name: str, account: str, region: str, version: str): return f"arn:{get_partition(region)}:lambda:{region}:{account}:layer:{layer_name}:{version}" -def parse_layer_arn(layer_version_arn: str) -> Tuple[str, str, str, str]: +def parse_layer_arn(layer_version_arn: str) -> tuple[str, str, str, str]: return LAYER_VERSION_ARN_PATTERN.match(layer_version_arn).group( "region_name", "account_id", "layer_name", "layer_version" ) diff --git a/localstack-core/localstack/services/lambda_/custom_endpoints.py b/localstack-core/localstack/services/lambda_/custom_endpoints.py index b8267f1e7d06b..b04077ad192b4 100644 --- a/localstack-core/localstack/services/lambda_/custom_endpoints.py +++ b/localstack-core/localstack/services/lambda_/custom_endpoints.py @@ -1,5 +1,5 @@ import urllib.parse -from typing import List, TypedDict +from typing import TypedDict from rolo import Request, route @@ -14,7 +14,7 @@ class LambdaRuntimesResponse(TypedDict, total=False): - Runtimes: List[Runtime] + Runtimes: list[Runtime] class LambdaCustomEndpoints: diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py b/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py index d5cab8d3300e9..89ae16ea15b16 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/esm_worker_factory.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable import botocore.config diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py b/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py index 644e99c264035..1e113ccc0df53 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/pipe_utils.py @@ -1,5 +1,5 @@ import json -from datetime import datetime, timezone +from datetime import UTC, datetime import botocore from botocore.client import BaseClient @@ -57,7 +57,7 @@ def get_standardized_service_name(service_name: str) -> str: def get_current_time() -> datetime: - return datetime.now(tz=timezone.utc) + return datetime.now(tz=UTC) def get_datetime_from_timestamp(timestamp: float) -> datetime: diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py b/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py index 07ef9a7d9cca5..fb1433519caa0 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/pollers/stream_poller.py @@ -4,8 +4,8 @@ from abc import abstractmethod from bisect import bisect_left from collections import defaultdict +from collections.abc import Iterator from datetime import datetime -from typing import Iterator from botocore.client import BaseClient from botocore.exceptions import ClientError diff --git a/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py b/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py index ab1180adbdd1d..7ad836d222496 100644 --- a/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py +++ b/localstack-core/localstack/services/lambda_/event_source_mapping/senders/sender_utils.py @@ -1,6 +1,7 @@ import sys +from collections.abc import Iterable, Iterator from itertools import islice -from typing import Any, Iterable, Iterator +from typing import Any def batched(iterable, n): diff --git a/localstack-core/localstack/services/lambda_/invocation/assignment.py b/localstack-core/localstack/services/lambda_/invocation/assignment.py index 216aaeb1a143c..d72505c6447ba 100644 --- a/localstack-core/localstack/services/lambda_/invocation/assignment.py +++ b/localstack-core/localstack/services/lambda_/invocation/assignment.py @@ -2,7 +2,6 @@ import logging from collections import defaultdict from concurrent.futures import Future, ThreadPoolExecutor -from typing import ContextManager from localstack.services.lambda_.invocation.execution_environment import ( EnvironmentStartupTimeoutException, @@ -44,7 +43,7 @@ def get_environment( version_manager_id: str, function_version: FunctionVersion, provisioning_type: InitializationType, - ) -> ContextManager[ExecutionEnvironment]: + ) -> contextlib.AbstractContextManager[ExecutionEnvironment]: applicable_envs = ( env for env in self.environments[version_manager_id].values() diff --git a/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py b/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py index 9fab3849ee098..83197864480ee 100644 --- a/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py +++ b/localstack-core/localstack/services/lambda_/invocation/docker_runtime_executor.py @@ -5,8 +5,9 @@ import tempfile import threading from collections import defaultdict +from collections.abc import Callable from pathlib import Path -from typing import Callable, Dict, Literal, Optional +from typing import Literal from localstack import config from localstack.aws.api.lambda_ import Architecture, PackageType, Runtime @@ -145,7 +146,7 @@ class RuntimeImageResolver: def __init__( self, default_resolve_fn: Callable[[Runtime], str] = get_default_image_for_runtime ): - self._mapping = dict() + self._mapping = {} self._default_resolve_fn = default_resolve_fn def _resolve(self, runtime: Runtime, custom_image_mapping: str = "") -> str: @@ -274,8 +275,8 @@ class LambdaContainerConfiguration(ContainerConfiguration): class DockerRuntimeExecutor(RuntimeExecutor): - ip: Optional[str] - executor_endpoint: Optional[ExecutorEndpoint] + ip: str | None + executor_endpoint: ExecutorEndpoint | None container_name: str def __init__(self, id: str, function_version: FunctionVersion) -> None: @@ -455,7 +456,7 @@ def get_endpoint_from_executor(self) -> str: def _get_networks_for_executor(self) -> list[str]: return get_all_container_networks_for_lambda() - def invoke(self, payload: Dict[str, str]): + def invoke(self, payload: dict[str, str]): LOG.debug( "Sending invoke-payload '%s' to executor '%s'", truncate(json.dumps(payload), config.LAMBDA_TRUNCATE_STDOUT), diff --git a/localstack-core/localstack/services/lambda_/invocation/execution_environment.py b/localstack-core/localstack/services/lambda_/invocation/execution_environment.py index ee5db4b0f5e93..3db4d308e234e 100644 --- a/localstack-core/localstack/services/lambda_/invocation/execution_environment.py +++ b/localstack-core/localstack/services/lambda_/invocation/execution_environment.py @@ -2,10 +2,10 @@ import random import string import time +from collections.abc import Callable from datetime import date, datetime from enum import Enum, auto from threading import RLock, Timer -from typing import Callable, Dict, Optional from localstack import config from localstack.aws.connect import connect_to @@ -61,8 +61,8 @@ class ExecutionEnvironment: status: RuntimeStatus initialization_type: InitializationType last_returned: datetime - startup_timer: Optional[Timer] - keepalive_timer: Optional[Timer] + startup_timer: Timer | None + keepalive_timer: Timer | None on_timeout: Callable[[str, str], None] def __init__( @@ -91,7 +91,7 @@ def get_log_group_name(self) -> str: def get_log_stream_name(self) -> str: return f"{date.today():%Y/%m/%d}/[{self.function_version.id.qualifier}]{self.id}" - def get_environment_variables(self) -> Dict[str, str]: + def get_environment_variables(self) -> dict[str, str]: """ Returns the environment variable set for the runtime container :return: Dict of environment variables diff --git a/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py b/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py index ded7d319e506d..08a6a7ce9d86d 100644 --- a/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py +++ b/localstack-core/localstack/services/lambda_/invocation/executor_endpoint.py @@ -3,7 +3,7 @@ import time from concurrent.futures import CancelledError, Future from http import HTTPStatus -from typing import Any, Dict, Optional +from typing import Any import requests from werkzeug import Request @@ -118,8 +118,8 @@ class ExecutorEndpoint(Endpoint): def __init__( self, executor_id: str, - container_address: Optional[str] = None, - container_port: Optional[int] = INVOCATION_PORT, + container_address: str | None = None, + container_port: int | None = INVOCATION_PORT, ) -> None: self.container_address = container_address self.container_port = container_port @@ -140,7 +140,7 @@ def invocation_error(self, request: Request, req_id: str) -> Response: def invocation_logs(self, request: Request, invoke_id: str) -> Response: logs = request.json - if isinstance(logs, Dict): + if isinstance(logs, dict): self.logs = logs["logs"] else: LOG.error("Invalid logs from init! Logs: %s", logs) @@ -183,7 +183,7 @@ def shutdown(self) -> None: if self.invocation_future: self.invocation_future.cancel() - def invoke(self, payload: Dict[str, str]) -> InvocationResult: + def invoke(self, payload: dict[str, str]) -> InvocationResult: self.invocation_future = Future() self.logs = None if not self.container_address: diff --git a/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py b/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py index 41da58b681701..45fddadf3155d 100644 --- a/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py +++ b/localstack-core/localstack/services/lambda_/invocation/internal_sqs_queue.py @@ -1,6 +1,6 @@ import logging import threading -from typing import Iterable +from collections.abc import Iterable from localstack import config from localstack.aws.api.sqs import ( diff --git a/localstack-core/localstack/services/lambda_/invocation/lambda_models.py b/localstack-core/localstack/services/lambda_/invocation/lambda_models.py index 3d7171ca8ca16..44065eb6f798d 100644 --- a/localstack-core/localstack/services/lambda_/invocation/lambda_models.py +++ b/localstack-core/localstack/services/lambda_/invocation/lambda_models.py @@ -12,7 +12,7 @@ from abc import ABCMeta, abstractmethod from datetime import datetime from pathlib import Path -from typing import IO, Dict, Literal, Optional, TypedDict +from typing import IO, Literal, TypedDict from botocore.exceptions import ClientError @@ -52,8 +52,8 @@ @dataclasses.dataclass(frozen=True) class VersionState: state: State - code: Optional[StateReasonCode] = None - reason: Optional[str] = None + code: StateReasonCode | None = None + reason: str | None = None @dataclasses.dataclass @@ -66,7 +66,7 @@ class Invocation: # = invocation_id request_id: str trace_context: dict - user_agent: Optional[str] = None + user_agent: str | None = None InitializationType = Literal["on-demand", "provisioned-concurrency"] @@ -352,11 +352,11 @@ class FunctionUrlConfig: url: str # full URL (e.g. "https://pfn5bdb2dl5mzkbn6eb2oi3xfe0nthdn.lambda-url.eu-west-3.on.aws/") auth_type: FunctionUrlAuthType creation_time: str # time - last_modified_time: Optional[str] = ( + last_modified_time: str | None = ( None # TODO: check if this is the creation time when initially creating ) - function_qualifier: Optional[str] = "$LATEST" # only $LATEST or alias name - invoke_mode: Optional[InvokeMode] = None + function_qualifier: str | None = "$LATEST" # only $LATEST or alias name + invoke_mode: InvokeMode | None = None @dataclasses.dataclass @@ -374,12 +374,12 @@ class ProvisionedConcurrencyState: status: ProvisionedConcurrencyStatusEnum = dataclasses.field( default=ProvisionedConcurrencyStatusEnum.IN_PROGRESS ) - status_reason: Optional[str] = None + status_reason: str | None = None @dataclasses.dataclass class AliasRoutingConfig: - version_weights: Dict[str, float] + version_weights: dict[str, float] @dataclasses.dataclass(frozen=True) @@ -431,10 +431,10 @@ class EventInvokeConfig: function_name: str qualifier: str - last_modified: Optional[str] = dataclasses.field(compare=False) - destination_config: Optional[DestinationConfig] = None - maximum_retry_attempts: Optional[int] = None - maximum_event_age_in_seconds: Optional[int] = None + last_modified: str | None = dataclasses.field(compare=False) + destination_config: DestinationConfig | None = None + maximum_retry_attempts: int | None = None + maximum_event_age_in_seconds: int | None = None # Result Models @@ -484,7 +484,7 @@ class CodeSigningConfig: allowed_publishers: AllowedPublishers policies: CodeSigningPolicies last_modified: str - description: Optional[str] = None + description: str | None = None @dataclasses.dataclass @@ -492,7 +492,7 @@ class LayerPolicyStatement: sid: str action: str principal: str - organization_id: Optional[str] + organization_id: str | None @dataclasses.dataclass @@ -551,18 +551,18 @@ class VersionFunctionConfiguration: last_modified: str # ISO string state: VersionState - image: Optional[ImageCode] = None - image_config: Optional[ImageConfig] = None - runtime_version_config: Optional[RuntimeVersionConfig] = None - last_update: Optional[UpdateStatus] = None + image: ImageCode | None = None + image_config: ImageConfig | None = None + runtime_version_config: RuntimeVersionConfig | None = None + last_update: UpdateStatus | None = None revision_id: str = dataclasses.field(init=False, default_factory=long_uid) layers: list[LayerVersion] = dataclasses.field(default_factory=list) - dead_letter_arn: Optional[str] = None + dead_letter_arn: str | None = None # kms_key_arn: str # file_system_configs: FileSystemConfig - vpc_config: Optional[VpcConfig] = None + vpc_config: VpcConfig | None = None logging_config: LoggingConfig = dataclasses.field(default_factory=dict) @@ -580,7 +580,7 @@ def qualified_arn(self) -> str: @dataclasses.dataclass class Function: function_name: str - code_signing_config_arn: Optional[str] = None + code_signing_config_arn: str | None = None aliases: dict[str, VersionAlias] = dataclasses.field(default_factory=dict) versions: dict[str, FunctionVersion] = dataclasses.field(default_factory=dict) function_url_configs: dict[str, FunctionUrlConfig] = dataclasses.field( @@ -592,7 +592,7 @@ class Function: event_invoke_configs: dict[str, EventInvokeConfig] = dataclasses.field( default_factory=dict ) # key is $LATEST(?), version or alias - reserved_concurrent_executions: Optional[int] = None + reserved_concurrent_executions: int | None = None recursive_loop: RecursiveLoop = RecursiveLoop.Terminate provisioned_concurrency_configs: dict[str, ProvisionedConcurrencyConfiguration] = ( dataclasses.field(default_factory=dict) diff --git a/localstack-core/localstack/services/lambda_/invocation/lambda_service.py b/localstack-core/localstack/services/lambda_/invocation/lambda_service.py index 051ab1b09b73a..c23d785744afd 100644 --- a/localstack-core/localstack/services/lambda_/invocation/lambda_service.py +++ b/localstack-core/localstack/services/lambda_/invocation/lambda_service.py @@ -11,7 +11,7 @@ from hashlib import sha256 from pathlib import PurePosixPath, PureWindowsPath from threading import RLock -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from localstack import config from localstack.aws.api.lambda_ import ( @@ -233,7 +233,7 @@ def invoke( request_id: str, payload: bytes | None, trace_context: dict | None = None, - user_agent: Optional[str] = None, + user_agent: str | None = None, ) -> InvocationResult | None: """ Invokes a specific version of a lambda @@ -627,7 +627,7 @@ def create_hot_reloading_code(path: str) -> HotReloadingCode: def store_s3_bucket_archive( archive_bucket: str, archive_key: str, - archive_version: Optional[str], + archive_version: str | None, function_name: str, region_name: str, account_id: str, @@ -646,7 +646,7 @@ def store_s3_bucket_archive( if archive_bucket == config.BUCKET_MARKER_LOCAL: hotreload_counter.labels(operation="create").increment() return create_hot_reloading_code(path=archive_key) - s3_client: "S3Client" = connect_to().s3 + s3_client: S3Client = connect_to().s3 kwargs = {"VersionId": archive_version} if archive_version else {} try: archive_file = s3_client.get_object(Bucket=archive_bucket, Key=archive_key, **kwargs)[ diff --git a/localstack-core/localstack/services/lambda_/invocation/logs.py b/localstack-core/localstack/services/lambda_/invocation/logs.py index 2ff2ab35d951b..32f7d579448d8 100644 --- a/localstack-core/localstack/services/lambda_/invocation/logs.py +++ b/localstack-core/localstack/services/lambda_/invocation/logs.py @@ -3,7 +3,6 @@ import threading import time from queue import Queue -from typing import Optional, Union from localstack.aws.connect import connect_to from localstack.utils.aws.client_types import ServicePrincipal @@ -28,9 +27,9 @@ class LogItem: class LogHandler: - log_queue: "Queue[Union[LogItem, ShutdownPill]]" + log_queue: "Queue[LogItem | ShutdownPill]" role_arn: str - _thread: Optional[FuncThread] + _thread: FuncThread | None _shutdown_event: threading.Event def __init__(self, role_arn: str, region: str) -> None: diff --git a/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py b/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py index 93ed5cc600532..59b37499f76c1 100644 --- a/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py +++ b/localstack-core/localstack/services/lambda_/invocation/runtime_executor.py @@ -2,7 +2,7 @@ import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Type, TypedDict +from typing import TypedDict from plux import PluginManager @@ -133,12 +133,12 @@ class ChmodPath(TypedDict): mode: str -EXECUTOR_PLUGIN_MANAGER: PluginManager[Type[RuntimeExecutor]] = PluginManager( +EXECUTOR_PLUGIN_MANAGER: PluginManager[type[RuntimeExecutor]] = PluginManager( RuntimeExecutorPlugin.namespace ) -def get_runtime_executor() -> Type[RuntimeExecutor]: +def get_runtime_executor() -> type[RuntimeExecutor]: plugin_name = config.LAMBDA_RUNTIME_EXECUTOR or "docker" if not EXECUTOR_PLUGIN_MANAGER.exists(plugin_name): LOG.warning( diff --git a/localstack-core/localstack/services/lambda_/ldm.py b/localstack-core/localstack/services/lambda_/ldm.py index 406fea1788d30..a5b10e48de9db 100644 --- a/localstack-core/localstack/services/lambda_/ldm.py +++ b/localstack-core/localstack/services/lambda_/ldm.py @@ -1,5 +1,4 @@ import abc -from typing import Optional from localstack.aws.api.lambda_ import Arn from localstack.services.lambda_.invocation.execution_environment import ExecutionEnvironment @@ -11,5 +10,5 @@ class LDMProvisioner(abc.ABC): @abc.abstractmethod def get_execution_environment( - self, qualified_lambda_arn: Arn, user_agent: Optional[str] - ) -> Optional[ExecutionEnvironment]: ... + self, qualified_lambda_arn: Arn, user_agent: str | None + ) -> ExecutionEnvironment | None: ... diff --git a/localstack-core/localstack/services/lambda_/packages.py b/localstack-core/localstack/services/lambda_/packages.py index 0600b4310ae30..a41e7e03bbcd5 100644 --- a/localstack-core/localstack/services/lambda_/packages.py +++ b/localstack-core/localstack/services/lambda_/packages.py @@ -4,7 +4,6 @@ import stat from functools import cache from pathlib import Path -from typing import List from localstack import config from localstack.packages import DownloadInstaller, InstallTarget, Package, PackageInstaller @@ -35,7 +34,7 @@ class LambdaRuntimePackage(Package): def __init__(self, default_version: str = LAMBDA_RUNTIME_VERSION): super().__init__(name="Lambda", default_version=default_version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [LAMBDA_RUNTIME_VERSION] def _get_installer(self, version: str) -> PackageInstaller: @@ -75,7 +74,7 @@ class LambdaJavaPackage(Package): def __init__(self): super().__init__("LambdaJavaLibs", "0.2.22") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["0.2.22", "0.2.21"] def _get_installer(self, version: str) -> PackageInstaller: diff --git a/localstack-core/localstack/services/lambda_/provider.py b/localstack-core/localstack/services/lambda_/provider.py index 8685ac5242ad0..b8a32707619fe 100644 --- a/localstack-core/localstack/services/lambda_/provider.py +++ b/localstack-core/localstack/services/lambda_/provider.py @@ -7,7 +7,7 @@ import re import threading import time -from typing import IO, Any, Optional, Tuple +from typing import IO, Any from botocore.exceptions import ClientError @@ -477,7 +477,7 @@ def _build_vpc_config( self, account_id: str, region_name: str, - vpc_config: Optional[dict] = None, + vpc_config: dict | None = None, ) -> VpcConfig | None: if not vpc_config or not is_api_enabled("ec2"): return None @@ -709,7 +709,7 @@ def _validate_layers(self, new_layers: list[str], region: str, account_id: str): "Cannot reference more than 5 layers.", Type="User" ) - visited_layers = dict() + visited_layers = {} for layer_version_arn in new_layers: ( layer_region, @@ -3589,7 +3589,7 @@ def update_function_event_invoke_config( @staticmethod def _resolve_layer( layer_name_or_arn: str, context: RequestContext - ) -> Tuple[str, str, str, Optional[str]]: + ) -> tuple[str, str, str, str | None]: """ Return locator attributes for a given Lambda layer. diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py index 8a23156e4ab13..c6044d26b564d 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,19 +14,19 @@ class LambdaCodeSigningConfigProperties(TypedDict): - AllowedPublishers: Optional[AllowedPublishers] - CodeSigningConfigArn: Optional[str] - CodeSigningConfigId: Optional[str] - CodeSigningPolicies: Optional[CodeSigningPolicies] - Description: Optional[str] + AllowedPublishers: AllowedPublishers | None + CodeSigningConfigArn: str | None + CodeSigningConfigId: str | None + CodeSigningPolicies: CodeSigningPolicies | None + Description: str | None class AllowedPublishers(TypedDict): - SigningProfileVersionArns: Optional[list[str]] + SigningProfileVersionArns: list[str] | None class CodeSigningPolicies(TypedDict): - UntrustedArtifactOnDeployment: Optional[str] + UntrustedArtifactOnDeployment: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py index b165c1253e910..7b9b830fbb5a1 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_codesigningconfig_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaCodeSigningConfigProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::Lambda::CodeSigningConfig" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_codesigningconfig import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py index 60dec55595e95..a084602b9b271 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig.py @@ -3,7 +3,7 @@ import uuid from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,25 +15,25 @@ class LambdaEventInvokeConfigProperties(TypedDict): - FunctionName: Optional[str] - Qualifier: Optional[str] - DestinationConfig: Optional[DestinationConfig] - Id: Optional[str] - MaximumEventAgeInSeconds: Optional[int] - MaximumRetryAttempts: Optional[int] + FunctionName: str | None + Qualifier: str | None + DestinationConfig: DestinationConfig | None + Id: str | None + MaximumEventAgeInSeconds: int | None + MaximumRetryAttempts: int | None class OnSuccess(TypedDict): - Destination: Optional[str] + Destination: str | None class OnFailure(TypedDict): - Destination: Optional[str] + Destination: str | None class DestinationConfig(TypedDict): - OnFailure: Optional[OnFailure] - OnSuccess: Optional[OnSuccess] + OnFailure: OnFailure | None + OnSuccess: OnSuccess | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py index 6ebda8450ef65..f921fa0283145 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventinvokeconfig_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaEventInvokeConfigProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::Lambda::EventInvokeConfig" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_eventinvokeconfig import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py index 1f82478526dd8..ea576c25edb17 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping.py @@ -3,7 +3,7 @@ import copy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,77 +15,77 @@ class LambdaEventSourceMappingProperties(TypedDict): - FunctionName: Optional[str] - AmazonManagedKafkaEventSourceConfig: Optional[AmazonManagedKafkaEventSourceConfig] - BatchSize: Optional[int] - BisectBatchOnFunctionError: Optional[bool] - DestinationConfig: Optional[DestinationConfig] - DocumentDBEventSourceConfig: Optional[DocumentDBEventSourceConfig] - Enabled: Optional[bool] - EventSourceArn: Optional[str] - FilterCriteria: Optional[FilterCriteria] - FunctionResponseTypes: Optional[list[str]] - Id: Optional[str] - MaximumBatchingWindowInSeconds: Optional[int] - MaximumRecordAgeInSeconds: Optional[int] - MaximumRetryAttempts: Optional[int] - ParallelizationFactor: Optional[int] - Queues: Optional[list[str]] - ScalingConfig: Optional[ScalingConfig] - SelfManagedEventSource: Optional[SelfManagedEventSource] - SelfManagedKafkaEventSourceConfig: Optional[SelfManagedKafkaEventSourceConfig] - SourceAccessConfigurations: Optional[list[SourceAccessConfiguration]] - StartingPosition: Optional[str] - StartingPositionTimestamp: Optional[float] - Topics: Optional[list[str]] - TumblingWindowInSeconds: Optional[int] + FunctionName: str | None + AmazonManagedKafkaEventSourceConfig: AmazonManagedKafkaEventSourceConfig | None + BatchSize: int | None + BisectBatchOnFunctionError: bool | None + DestinationConfig: DestinationConfig | None + DocumentDBEventSourceConfig: DocumentDBEventSourceConfig | None + Enabled: bool | None + EventSourceArn: str | None + FilterCriteria: FilterCriteria | None + FunctionResponseTypes: list[str] | None + Id: str | None + MaximumBatchingWindowInSeconds: int | None + MaximumRecordAgeInSeconds: int | None + MaximumRetryAttempts: int | None + ParallelizationFactor: int | None + Queues: list[str] | None + ScalingConfig: ScalingConfig | None + SelfManagedEventSource: SelfManagedEventSource | None + SelfManagedKafkaEventSourceConfig: SelfManagedKafkaEventSourceConfig | None + SourceAccessConfigurations: list[SourceAccessConfiguration] | None + StartingPosition: str | None + StartingPositionTimestamp: float | None + Topics: list[str] | None + TumblingWindowInSeconds: int | None class OnFailure(TypedDict): - Destination: Optional[str] + Destination: str | None class DestinationConfig(TypedDict): - OnFailure: Optional[OnFailure] + OnFailure: OnFailure | None class Filter(TypedDict): - Pattern: Optional[str] + Pattern: str | None class FilterCriteria(TypedDict): - Filters: Optional[list[Filter]] + Filters: list[Filter] | None class SourceAccessConfiguration(TypedDict): - Type: Optional[str] - URI: Optional[str] + Type: str | None + URI: str | None class Endpoints(TypedDict): - KafkaBootstrapServers: Optional[list[str]] + KafkaBootstrapServers: list[str] | None class SelfManagedEventSource(TypedDict): - Endpoints: Optional[Endpoints] + Endpoints: Endpoints | None class AmazonManagedKafkaEventSourceConfig(TypedDict): - ConsumerGroupId: Optional[str] + ConsumerGroupId: str | None class SelfManagedKafkaEventSourceConfig(TypedDict): - ConsumerGroupId: Optional[str] + ConsumerGroupId: str | None class ScalingConfig(TypedDict): - MaximumConcurrency: Optional[int] + MaximumConcurrency: int | None class DocumentDBEventSourceConfig(TypedDict): - CollectionName: Optional[str] - DatabaseName: Optional[str] - FullDocument: Optional[str] + CollectionName: str | None + DatabaseName: str | None + FullDocument: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py index f4dd5b69a5423..db2e1b238f364 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_eventsourcemapping_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaEventSourceMappingProviderPlugin(CloudFormationResourceProviderPlugi name = "AWS::Lambda::EventSourceMapping" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_eventsourcemapping import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py index bbcc61e335934..21342d9d55a76 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function.py @@ -3,7 +3,7 @@ import os from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -20,98 +20,98 @@ class LambdaFunctionProperties(TypedDict): - Code: Optional[Code] - Role: Optional[str] - Architectures: Optional[list[str]] - Arn: Optional[str] - CodeSigningConfigArn: Optional[str] - DeadLetterConfig: Optional[DeadLetterConfig] - Description: Optional[str] - Environment: Optional[Environment] - EphemeralStorage: Optional[EphemeralStorage] - FileSystemConfigs: Optional[list[FileSystemConfig]] - FunctionName: Optional[str] - Handler: Optional[str] - ImageConfig: Optional[ImageConfig] - KmsKeyArn: Optional[str] - Layers: Optional[list[str]] - MemorySize: Optional[int] - PackageType: Optional[str] - ReservedConcurrentExecutions: Optional[int] - Runtime: Optional[str] - RuntimeManagementConfig: Optional[RuntimeManagementConfig] - SnapStart: Optional[SnapStart] - SnapStartResponse: Optional[SnapStartResponse] - Tags: Optional[list[Tag]] - Timeout: Optional[int] - TracingConfig: Optional[TracingConfig] - VpcConfig: Optional[VpcConfig] + Code: Code | None + Role: str | None + Architectures: list[str] | None + Arn: str | None + CodeSigningConfigArn: str | None + DeadLetterConfig: DeadLetterConfig | None + Description: str | None + Environment: Environment | None + EphemeralStorage: EphemeralStorage | None + FileSystemConfigs: list[FileSystemConfig] | None + FunctionName: str | None + Handler: str | None + ImageConfig: ImageConfig | None + KmsKeyArn: str | None + Layers: list[str] | None + MemorySize: int | None + PackageType: str | None + ReservedConcurrentExecutions: int | None + Runtime: str | None + RuntimeManagementConfig: RuntimeManagementConfig | None + SnapStart: SnapStart | None + SnapStartResponse: SnapStartResponse | None + Tags: list[Tag] | None + Timeout: int | None + TracingConfig: TracingConfig | None + VpcConfig: VpcConfig | None class TracingConfig(TypedDict): - Mode: Optional[str] + Mode: str | None class VpcConfig(TypedDict): - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class RuntimeManagementConfig(TypedDict): - UpdateRuntimeOn: Optional[str] - RuntimeVersionArn: Optional[str] + UpdateRuntimeOn: str | None + RuntimeVersionArn: str | None class SnapStart(TypedDict): - ApplyOn: Optional[str] + ApplyOn: str | None class FileSystemConfig(TypedDict): - Arn: Optional[str] - LocalMountPath: Optional[str] + Arn: str | None + LocalMountPath: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ImageConfig(TypedDict): - Command: Optional[list[str]] - EntryPoint: Optional[list[str]] - WorkingDirectory: Optional[str] + Command: list[str] | None + EntryPoint: list[str] | None + WorkingDirectory: str | None class DeadLetterConfig(TypedDict): - TargetArn: Optional[str] + TargetArn: str | None class SnapStartResponse(TypedDict): - ApplyOn: Optional[str] - OptimizationStatus: Optional[str] + ApplyOn: str | None + OptimizationStatus: str | None class Code(TypedDict): - ImageUri: Optional[str] - S3Bucket: Optional[str] - S3Key: Optional[str] - S3ObjectVersion: Optional[str] - ZipFile: Optional[str] + ImageUri: str | None + S3Bucket: str | None + S3Key: str | None + S3ObjectVersion: str | None + ZipFile: str | None class LoggingConfig(TypedDict): - ApplicationLogLevel: Optional[str] - LogFormat: Optional[str] - LogGroup: Optional[str] - SystemLogLevel: Optional[str] + ApplicationLogLevel: str | None + LogFormat: str | None + LogGroup: str | None + SystemLogLevel: str | None class Environment(TypedDict): - Variables: Optional[dict] + Variables: dict | None class EphemeralStorage(TypedDict): - Size: Optional[int] + Size: int | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py index 2f07aaca0e74b..074a1fd9ff312 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_function_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaFunctionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Function" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_function import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion.py index 3e8e2ecb4811c..196fb12377549 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion.py @@ -3,7 +3,7 @@ import logging from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -20,19 +20,19 @@ class LambdaLayerVersionProperties(TypedDict): - Content: Optional[Content] - CompatibleArchitectures: Optional[list[str]] - CompatibleRuntimes: Optional[list[str]] - Description: Optional[str] - LayerName: Optional[str] - LayerVersionArn: Optional[str] - LicenseInfo: Optional[str] + Content: Content | None + CompatibleArchitectures: list[str] | None + CompatibleRuntimes: list[str] | None + Description: str | None + LayerName: str | None + LayerVersionArn: str | None + LicenseInfo: str | None class Content(TypedDict): - S3Bucket: Optional[str] - S3Key: Optional[str] - S3ObjectVersion: Optional[str] + S3Bucket: str | None + S3Key: str | None + S3ObjectVersion: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion_plugin.py index 7ebe11a9c7647..09d316c7c5e7d 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversion_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaLayerVersionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::LayerVersion" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_layerversion import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission.py index e6622141a165c..0a73c414462f9 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,11 +14,11 @@ class LambdaLayerVersionPermissionProperties(TypedDict): - Action: Optional[str] - LayerVersionArn: Optional[str] - Principal: Optional[str] - Id: Optional[str] - OrganizationId: Optional[str] + Action: str | None + LayerVersionArn: str | None + Principal: str | None + Id: str | None + OrganizationId: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission_plugin.py index 339765439b293..016dacb49fbff 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_layerversionpermission_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaLayerVersionPermissionProviderPlugin(CloudFormationResourceProviderP name = "AWS::Lambda::LayerVersionPermission" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_layerversionpermission import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission.py index 315e5a015502e..31ee49ea6d98b 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,15 +15,15 @@ class LambdaPermissionProperties(TypedDict): - Action: Optional[str] - FunctionName: Optional[str] - Principal: Optional[str] - EventSourceToken: Optional[str] - FunctionUrlAuthType: Optional[str] - Id: Optional[str] - PrincipalOrgID: Optional[str] - SourceAccount: Optional[str] - SourceArn: Optional[str] + Action: str | None + FunctionName: str | None + Principal: str | None + EventSourceToken: str | None + FunctionUrlAuthType: str | None + Id: str | None + PrincipalOrgID: str | None + SourceAccount: str | None + SourceArn: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission_plugin.py index 4a06f49c62ee4..d1ae0ca1ead2a 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_permission_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaPermissionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Permission" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_permission import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url.py index c9b157dd26a89..fc3a28ec5e0e5 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,22 +14,22 @@ class LambdaUrlProperties(TypedDict): - AuthType: Optional[str] - TargetFunctionArn: Optional[str] - Cors: Optional[Cors] - FunctionArn: Optional[str] - FunctionUrl: Optional[str] - InvokeMode: Optional[str] - Qualifier: Optional[str] + AuthType: str | None + TargetFunctionArn: str | None + Cors: Cors | None + FunctionArn: str | None + FunctionUrl: str | None + InvokeMode: str | None + Qualifier: str | None class Cors(TypedDict): - AllowCredentials: Optional[bool] - AllowHeaders: Optional[list[str]] - AllowMethods: Optional[list[str]] - AllowOrigins: Optional[list[str]] - ExposeHeaders: Optional[list[str]] - MaxAge: Optional[int] + AllowCredentials: bool | None + AllowHeaders: list[str] | None + AllowMethods: list[str] | None + AllowOrigins: list[str] | None + ExposeHeaders: list[str] | None + MaxAge: int | None REPEATED_INVOCATION = "repeated_invocation" @@ -67,7 +67,7 @@ def create( """ model = request.desired_state lambda_client = request.aws_client_factory.lambda_ - params = util.select_attributes(model, ["Qualifier", "Cors", "AuthType"]) + params = util.select_attributes(model, ["Qualifier", "Cors", "AuthType", "InvokeMode"]) params["FunctionName"] = model["TargetFunctionArn"] response = lambda_client.create_function_url_config(**params) diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url_plugin.py index afa2d4a2b92b5..81b2948bc4d63 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_url_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaUrlProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Url" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_url import LambdaUrlProvider diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version.py index adc04756a59c5..12dadec1f2d4d 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,16 +14,16 @@ class LambdaVersionProperties(TypedDict): - FunctionName: Optional[str] - CodeSha256: Optional[str] - Description: Optional[str] - Id: Optional[str] - ProvisionedConcurrencyConfig: Optional[ProvisionedConcurrencyConfiguration] - Version: Optional[str] + FunctionName: str | None + CodeSha256: str | None + Description: str | None + Id: str | None + ProvisionedConcurrencyConfig: ProvisionedConcurrencyConfiguration | None + Version: str | None class ProvisionedConcurrencyConfiguration(TypedDict): - ProvisionedConcurrentExecutions: Optional[int] + ProvisionedConcurrentExecutions: int | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version_plugin.py index ad6e92edb426d..e6af1c6e2dd7f 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/aws_lambda_version_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaVersionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Version" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.aws_lambda_version import ( diff --git a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py index 044eeed162845..2130958dde803 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,26 +14,26 @@ class LambdaAliasProperties(TypedDict): - FunctionName: Optional[str] - FunctionVersion: Optional[str] - Name: Optional[str] - Description: Optional[str] - Id: Optional[str] - ProvisionedConcurrencyConfig: Optional[ProvisionedConcurrencyConfiguration] - RoutingConfig: Optional[AliasRoutingConfiguration] + FunctionName: str | None + FunctionVersion: str | None + Name: str | None + Description: str | None + Id: str | None + ProvisionedConcurrencyConfig: ProvisionedConcurrencyConfiguration | None + RoutingConfig: AliasRoutingConfiguration | None class ProvisionedConcurrencyConfiguration(TypedDict): - ProvisionedConcurrentExecutions: Optional[int] + ProvisionedConcurrentExecutions: int | None class VersionWeight(TypedDict): - FunctionVersion: Optional[str] - FunctionWeight: Optional[float] + FunctionVersion: str | None + FunctionWeight: float | None class AliasRoutingConfiguration(TypedDict): - AdditionalVersionWeights: Optional[list[VersionWeight]] + AdditionalVersionWeights: list[VersionWeight] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py index 406b05deddd45..e1b5120633dd7 100644 --- a/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py +++ b/localstack-core/localstack/services/lambda_/resource_providers/lambda_alias_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LambdaAliasProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Lambda::Alias" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.lambda_.resource_providers.lambda_alias import LambdaAliasProvider diff --git a/localstack-core/localstack/services/lambda_/runtimes.py b/localstack-core/localstack/services/lambda_/runtimes.py index 3fa96216257f6..3e666949dd008 100644 --- a/localstack-core/localstack/services/lambda_/runtimes.py +++ b/localstack-core/localstack/services/lambda_/runtimes.py @@ -1,7 +1,5 @@ """This Lambda Runtimes reference defines everything around Lambda runtimes to facilitate adding new runtimes.""" -from typing import Optional - from localstack.aws.api.lambda_ import Runtime # LocalStack Lambda runtimes support policy @@ -77,7 +75,7 @@ # We currently use them in LocalStack logs as bonus recommendation (DevX). # When updating the recommendation, # please regenerate all tests with @markers.lambda_runtime_update -DEPRECATED_RUNTIMES_UPGRADES: dict[Runtime, Optional[Runtime]] = { +DEPRECATED_RUNTIMES_UPGRADES: dict[Runtime, Runtime | None] = { # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024 Runtime.java8: Runtime.java21, # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024 diff --git a/localstack-core/localstack/services/lambda_/urlrouter.py b/localstack-core/localstack/services/lambda_/urlrouter.py index 992909d0e57a2..c43fd52a7fb5f 100644 --- a/localstack-core/localstack/services/lambda_/urlrouter.py +++ b/localstack-core/localstack/services/lambda_/urlrouter.py @@ -5,6 +5,7 @@ import logging from datetime import datetime from http import HTTPStatus +from json import JSONDecodeError from rolo.request import restore_payload @@ -192,7 +193,18 @@ def lambda_result_to_response(result: InvocationResult): ) original_payload = to_str(result.payload) - parsed_result = json.loads(original_payload) + try: + parsed_result = json.loads(original_payload) + except JSONDecodeError: + # URL router must be able to parse a Streaming Response without necessary defining it in the URL Config + # And if the body is a simple string, it should be returned without issues + split_index = original_payload.find("\x00" * 8) + if split_index == -1: + parsed_result = {"body": original_payload} + else: + metadata = original_payload[:split_index] + body_str = original_payload[split_index + 8 :] + parsed_result = {**json.loads(metadata), "body": body_str} # patch to fix whitespaces # TODO: check if this is a downstream issue of invocation result serialization @@ -201,6 +213,7 @@ def lambda_result_to_response(result: InvocationResult): if isinstance(parsed_result, str): # a string is a special case here and is returned as-is response.data = parsed_result + elif isinstance(parsed_result, dict): # if it's a dict it might be a proper response if isinstance(parsed_result.get("headers"), dict): diff --git a/localstack-core/localstack/services/logs/models.py b/localstack-core/localstack/services/logs/models.py index 5e2ba973cab93..2fb082085ff62 100644 --- a/localstack-core/localstack/services/logs/models.py +++ b/localstack-core/localstack/services/logs/models.py @@ -1,5 +1,3 @@ -from typing import Dict - from moto.logs.models import LogsBackend as MotoLogsBackend from moto.logs.models import logs_backends as moto_logs_backend @@ -12,7 +10,7 @@ def get_moto_logs_backend(account_id: str, region_name: str) -> MotoLogsBackend: class LogsStore(BaseStore): # maps resource ARN to tags - TAGS: Dict[str, Dict[str, str]] = CrossRegionAttribute(default=dict) + TAGS: dict[str, dict[str, str]] = CrossRegionAttribute(default=dict) logs_stores = AccountRegionBundle("logs", LogsStore) diff --git a/localstack-core/localstack/services/logs/provider.py b/localstack-core/localstack/services/logs/provider.py index 2ded5f5d31f0d..b77bc646a3dfd 100644 --- a/localstack-core/localstack/services/logs/provider.py +++ b/localstack-core/localstack/services/logs/provider.py @@ -3,8 +3,8 @@ import io import json import logging +from collections.abc import Callable from gzip import GzipFile -from typing import Callable, Dict from moto.core.utils import unix_time_millis from moto.logs.models import LogEvent, LogsBackend @@ -243,7 +243,7 @@ def _check_resource_arn_tagging(self, resource_arn): ) -def get_pattern_matcher(pattern: str) -> Callable[[str, Dict], bool]: +def get_pattern_matcher(pattern: str) -> Callable[[str, dict], bool]: """Returns a pattern matcher. Can be patched by plugins to return a more sophisticated pattern matcher.""" return lambda _pattern, _log_event: True @@ -410,7 +410,7 @@ def moto_put_log_events(self: "MotoLogStream", log_events): Record={"Data": payload_gz_encoded}, ) - return "{:056d}".format(self.upload_sequence_token) + return f"{self.upload_sequence_token:056d}" @patch(MotoLogStream.filter_log_events) diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py index 6dd6b66190bf3..a5e7bca25cfd8 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class LogsLogGroupProperties(TypedDict): - Arn: Optional[str] - DataProtectionPolicy: Optional[dict] - KmsKeyId: Optional[str] - LogGroupName: Optional[str] - RetentionInDays: Optional[int] - Tags: Optional[list[Tag]] + Arn: str | None + DataProtectionPolicy: dict | None + KmsKeyId: str | None + LogGroupName: str | None + RetentionInDays: int | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py index 5dd8087a87561..46899a144adba 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_loggroup_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LogsLogGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Logs::LogGroup" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.logs.resource_providers.aws_logs_loggroup import ( diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py index 4cb21339b6e77..bc36c8dc5c0e2 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,9 +14,9 @@ class LogsLogStreamProperties(TypedDict): - LogGroupName: Optional[str] - Id: Optional[str] - LogStreamName: Optional[str] + LogGroupName: str | None + Id: str | None + LogStreamName: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py index 578e23c4ae628..51dde35fc97ca 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_logstream_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LogsLogStreamProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Logs::LogStream" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.logs.resource_providers.aws_logs_logstream import ( diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py index 26f204e52e78e..28134dbfedf0d 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,12 +14,12 @@ class LogsSubscriptionFilterProperties(TypedDict): - DestinationArn: Optional[str] - FilterPattern: Optional[str] - LogGroupName: Optional[str] - Distribution: Optional[str] - FilterName: Optional[str] - RoleArn: Optional[str] + DestinationArn: str | None + FilterPattern: str | None + LogGroupName: str | None + Distribution: str | None + FilterName: str | None + RoleArn: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py index def55ff386045..7055e96585c43 100644 --- a/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py +++ b/localstack-core/localstack/services/logs/resource_providers/aws_logs_subscriptionfilter_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class LogsSubscriptionFilterProviderPlugin(CloudFormationResourceProviderPlugin) name = "AWS::Logs::SubscriptionFilter" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.logs.resource_providers.aws_logs_subscriptionfilter import ( diff --git a/localstack-core/localstack/services/moto.py b/localstack-core/localstack/services/moto.py index c98989c39a967..f14fe915d4ceb 100644 --- a/localstack-core/localstack/services/moto.py +++ b/localstack-core/localstack/services/moto.py @@ -4,8 +4,8 @@ import copy import sys +from collections.abc import Callable from functools import lru_cache -from typing import Callable, Optional, Union import moto.backends as moto_backends from moto.core.base_backend import BackendDict @@ -76,7 +76,7 @@ def call_moto_with_request( def _proxy_moto( context: RequestContext, request: ServiceRequest -) -> Optional[Union[ServiceResponse, Response]]: +) -> ServiceResponse | Response | None: """ Wraps `call_moto` such that the interface is compliant with a ServiceRequestHandler. @@ -154,7 +154,7 @@ def get_dispatcher(service: str, path: str) -> MotoDispatcher: return endpoint -@lru_cache() +@lru_cache def get_moto_routing_table(service: str) -> Map: """Cached version of load_moto_routing_table.""" return load_moto_routing_table(service) diff --git a/localstack-core/localstack/services/opensearch/cluster.py b/localstack-core/localstack/services/opensearch/cluster.py index cae1916c90b09..f8f4c54d8cd76 100644 --- a/localstack-core/localstack/services/opensearch/cluster.py +++ b/localstack-core/localstack/services/opensearch/cluster.py @@ -2,7 +2,7 @@ import logging import os import threading -from typing import Dict, List, NamedTuple, Optional, Tuple +from typing import NamedTuple from urllib.parse import urlparse import requests @@ -37,7 +37,7 @@ INTERNAL_USER_AUTH = ("localstack-internal", "localstack-internal") DEFAULT_BACKEND_HOST = "127.0.0.1" -CommandSettings = Dict[str, str] +CommandSettings = dict[str, str] class Directories(NamedTuple): @@ -49,8 +49,8 @@ class Directories(NamedTuple): def get_cluster_health_status( - url: str, auth: Tuple[str, str] | None, host: str | None = None -) -> Optional[str]: + url: str, auth: tuple[str, str] | None, host: str | None = None +) -> str | None: """ Queries the health endpoint of OpenSearch/Elasticsearch and returns either the status ('green', 'yellow', ...) or None if the response returned a non-200 response. @@ -125,7 +125,7 @@ def resolve_directories(version: str, cluster_path: str, data_root: str = None) return Directories(install_dir, tmp_dir, modules_dir, data_dir, backup_dir) -def build_cluster_run_command(cluster_bin: str, settings: CommandSettings) -> List[str]: +def build_cluster_run_command(cluster_bin: str, settings: CommandSettings) -> list[str]: """ Takes the command settings dict and builds the actual command (which can then be executed as a shell command). @@ -169,13 +169,13 @@ class SecurityOptions: master_password: str | None @property - def auth(self) -> Tuple[str, str] | None: + def auth(self) -> tuple[str, str] | None: """Returns an auth tuple which can be used for HTTP requests or None, if disabled.""" return None if not self.enabled else (self.master_username, self.master_password) @staticmethod def from_input( - advanced_security_options: Optional[AdvancedSecurityOptionsInput], + advanced_security_options: AdvancedSecurityOptionsInput | None, ) -> "SecurityOptions": """ Parses the given AdvancedSecurityOptionsInput, performs some validation, and returns the parsed SecurityOptions. @@ -215,7 +215,7 @@ def from_input( def register_cluster( host: str, path: str, forward_url: str, custom_endpoint: CustomEndpoint -) -> List[Rule]: +) -> list[Rule]: """ Registers routes for a cluster at the edge router. Depending on which endpoint strategy is employed, and if a custom endpoint is enabled, different routes are @@ -338,7 +338,7 @@ def bin_name(self) -> str: def os_user(self): return constants.OS_USER_OPENSEARCH - def health(self) -> Optional[str]: + def health(self) -> str | None: return get_cluster_health_status(self.url, auth=self.auth) def do_start_thread(self) -> FuncThread: @@ -451,8 +451,8 @@ def _base_settings(self, dirs) -> CommandSettings: return settings def _create_run_command( - self, directories: Directories, additional_settings: Optional[CommandSettings] = None - ) -> List[str]: + self, directories: Directories, additional_settings: CommandSettings | None = None + ) -> list[str]: # delete opensearch data that may be cached locally from a previous test run bin_path = os.path.join(directories.install, "bin", self.bin_name) @@ -464,7 +464,7 @@ def _create_run_command( cmd = build_cluster_run_command(bin_path, settings) return cmd - def _create_env_vars(self, directories: Directories) -> Dict: + def _create_env_vars(self, directories: Directories) -> dict: env_vars = { "JAVA_HOME": os.path.join(directories.install, "jdk"), "OPENSEARCH_JAVA_OPTS": os.environ.get("OPENSEARCH_JAVA_OPTS", "-Xms200m -Xmx600m"), @@ -699,7 +699,7 @@ def _base_settings(self, dirs) -> CommandSettings: return settings - def _create_env_vars(self, directories: Directories) -> Dict: + def _create_env_vars(self, directories: Directories) -> dict: return { **elasticsearch_package.get_installer(self.version).get_java_env_vars(), "ES_JAVA_OPTS": os.environ.get("ES_JAVA_OPTS", "-Xms200m -Xmx600m"), diff --git a/localstack-core/localstack/services/opensearch/cluster_manager.py b/localstack-core/localstack/services/opensearch/cluster_manager.py index 8a286daf661fc..1ceb42ed64c18 100644 --- a/localstack-core/localstack/services/opensearch/cluster_manager.py +++ b/localstack-core/localstack/services/opensearch/cluster_manager.py @@ -1,7 +1,6 @@ import dataclasses import logging import threading -from typing import Dict, Optional from botocore.utils import ArnParser @@ -80,9 +79,9 @@ def from_arn(arn: str) -> "DomainKey": def build_cluster_endpoint( domain_key: DomainKey, - custom_endpoint: Optional[CustomEndpoint] = None, + custom_endpoint: CustomEndpoint | None = None, engine_type: EngineType = EngineType.OpenSearch, - preferred_port: Optional[int] = None, + preferred_port: int | None = None, ) -> str: """ Builds the cluster endpoint from and optional custom_endpoint and the localstack opensearch config. Example @@ -131,7 +130,7 @@ def build_cluster_endpoint( def determine_custom_endpoint( domain_endpoint_options: DomainEndpointOptions, -) -> Optional[CustomEndpoint]: +) -> CustomEndpoint | None: if not domain_endpoint_options: return @@ -146,7 +145,7 @@ def determine_custom_endpoint( class ClusterManager: - clusters: Dict[str, Server] + clusters: dict[str, Server] def __init__(self) -> None: self.clusters = {} @@ -155,9 +154,9 @@ def create( self, arn: str, version: str, - endpoint_options: Optional[DomainEndpointOptions] = None, - security_options: Optional[SecurityOptions] = None, - preferred_port: Optional[int] = None, + endpoint_options: DomainEndpointOptions | None = None, + security_options: SecurityOptions | None = None, + preferred_port: int | None = None, ) -> Server: """ Creates a new cluster. @@ -190,7 +189,7 @@ def create( self.clusters[arn] = cluster return cluster - def get(self, arn: str) -> Optional[Server]: + def get(self, arn: str) -> Server | None: return self.clusters.get(arn) def remove(self, arn: str): @@ -257,8 +256,8 @@ class MultiplexingClusterManager(ClusterManager): - OPENSEARCH_ENDPOINT_STRATEGY = domain / path """ - cluster: Optional[Server] - endpoints: Dict[str, ClusterEndpoint] + cluster: Server | None + endpoints: dict[str, ClusterEndpoint] def __init__(self) -> None: super().__init__() @@ -366,7 +365,7 @@ class SingletonClusterManager(ClusterManager): - ES_MULTI_CLUSTER == False """ - cluster: Optional[Server] + cluster: Server | None def __init__(self) -> None: super().__init__() @@ -378,8 +377,8 @@ def create( self, arn: str, version: str, - endpoint_options: Optional[DomainEndpointOptions] = None, - security_options: Optional[SecurityOptions] = None, + endpoint_options: DomainEndpointOptions | None = None, + security_options: SecurityOptions | None = None, preferred_port: int = None, ) -> Server: with self.mutex: diff --git a/localstack-core/localstack/services/opensearch/models.py b/localstack-core/localstack/services/opensearch/models.py index 5748ac7639ec3..3141e4263cb4f 100644 --- a/localstack-core/localstack/services/opensearch/models.py +++ b/localstack-core/localstack/services/opensearch/models.py @@ -1,5 +1,3 @@ -from typing import Dict - from localstack.aws.api.opensearch import DomainStatus from localstack.services.stores import ( AccountRegionBundle, @@ -12,7 +10,7 @@ class OpenSearchStore(BaseStore): # storage for domain resources (access should be protected with the _domain_mutex) - opensearch_domains: Dict[str, DomainStatus] = LocalAttribute(default=dict) + opensearch_domains: dict[str, DomainStatus] = LocalAttribute(default=dict) # static tagging service instance TAGS = CrossRegionAttribute(default=TaggingService) diff --git a/localstack-core/localstack/services/opensearch/packages.py b/localstack-core/localstack/services/opensearch/packages.py index 35a7fd933ea91..8b0e91368b256 100644 --- a/localstack-core/localstack/services/opensearch/packages.py +++ b/localstack-core/localstack/services/opensearch/packages.py @@ -5,7 +5,6 @@ import shutil import textwrap import threading -from typing import List import semver @@ -49,7 +48,7 @@ def _get_installer(self, version: str) -> PackageInstaller: else: return OpensearchPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return list(versions.install_versions.keys()) diff --git a/localstack-core/localstack/services/opensearch/provider.py b/localstack-core/localstack/services/opensearch/provider.py index e33554c347ad8..aef2123335287 100644 --- a/localstack-core/localstack/services/opensearch/provider.py +++ b/localstack-core/localstack/services/opensearch/provider.py @@ -3,9 +3,8 @@ import re import threading from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from random import randint -from typing import Dict, Optional from urllib.parse import urlparse from localstack import config @@ -143,9 +142,9 @@ def _run_cluster_startup_monitor(cluster: Server, domain_name: str, region: str) def create_cluster( domain_key: DomainKey, engine_version: str, - domain_endpoint_options: Optional[DomainEndpointOptions], - security_options: Optional[SecurityOptions], - preferred_port: Optional[int] = None, + domain_endpoint_options: DomainEndpointOptions | None, + security_options: SecurityOptions | None, + preferred_port: int | None = None, ): """ Uses the ClusterManager to create a new cluster for the given domain key. NOT thread safe, needs to be called @@ -369,7 +368,7 @@ def get_domain_status( Cancellable=False, UpdateStatus=DeploymentStatus.COMPLETED, Description="There is no software update available for this domain.", - AutomatedUpdateDate=datetime.fromtimestamp(0, tz=timezone.utc), + AutomatedUpdateDate=datetime.fromtimestamp(0, tz=UTC), OptionalDeployment=True, ), DomainEndpointOptions=stored_status.get("DomainEndpointOptions") @@ -399,7 +398,7 @@ def _ensure_domain_exists(arn: ARN) -> None: def _update_domain_config_request_to_status(request: UpdateDomainConfigRequest) -> DomainStatus: - request: Dict + request: dict request.pop("DryRun", None) request.pop("DomainName", None) return request @@ -581,7 +580,7 @@ def update_domain_config( if domain_status is None: raise ResourceNotFoundException(f"Domain not found: {domain_key.domain_name}") - status_update: Dict = _update_domain_config_request_to_status(payload) + status_update: dict = _update_domain_config_request_to_status(payload) domain_status.update(status_update) return UpdateDomainConfigResponse(DomainConfig=_status_to_config(domain_status)) diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py index 4de950b722ce9..610b6df18e125 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain.py @@ -3,7 +3,7 @@ import copy from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.aws.api.es import CreateElasticsearchDomainRequest @@ -17,105 +17,105 @@ class ElasticsearchDomainProperties(TypedDict): - AccessPolicies: Optional[dict] - AdvancedOptions: Optional[dict] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - Arn: Optional[str] - CognitoOptions: Optional[CognitoOptions] - DomainArn: Optional[str] - DomainEndpoint: Optional[str] - DomainEndpointOptions: Optional[DomainEndpointOptions] - DomainName: Optional[str] - EBSOptions: Optional[EBSOptions] - ElasticsearchClusterConfig: Optional[ElasticsearchClusterConfig] - ElasticsearchVersion: Optional[str] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - Id: Optional[str] - LogPublishingOptions: Optional[dict] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - SnapshotOptions: Optional[SnapshotOptions] - Tags: Optional[list[Tag]] - VPCOptions: Optional[VPCOptions] + AccessPolicies: dict | None + AdvancedOptions: dict | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + Arn: str | None + CognitoOptions: CognitoOptions | None + DomainArn: str | None + DomainEndpoint: str | None + DomainEndpointOptions: DomainEndpointOptions | None + DomainName: str | None + EBSOptions: EBSOptions | None + ElasticsearchClusterConfig: ElasticsearchClusterConfig | None + ElasticsearchVersion: str | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + Id: str | None + LogPublishingOptions: dict | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + SnapshotOptions: SnapshotOptions | None + Tags: list[Tag] | None + VPCOptions: VPCOptions | None class ZoneAwarenessConfig(TypedDict): - AvailabilityZoneCount: Optional[int] + AvailabilityZoneCount: int | None class ColdStorageOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class ElasticsearchClusterConfig(TypedDict): - ColdStorageOptions: Optional[ColdStorageOptions] - DedicatedMasterCount: Optional[int] - DedicatedMasterEnabled: Optional[bool] - DedicatedMasterType: Optional[str] - InstanceCount: Optional[int] - InstanceType: Optional[str] - WarmCount: Optional[int] - WarmEnabled: Optional[bool] - WarmType: Optional[str] - ZoneAwarenessConfig: Optional[ZoneAwarenessConfig] - ZoneAwarenessEnabled: Optional[bool] + ColdStorageOptions: ColdStorageOptions | None + DedicatedMasterCount: int | None + DedicatedMasterEnabled: bool | None + DedicatedMasterType: str | None + InstanceCount: int | None + InstanceType: str | None + WarmCount: int | None + WarmEnabled: bool | None + WarmType: str | None + ZoneAwarenessConfig: ZoneAwarenessConfig | None + ZoneAwarenessEnabled: bool | None class SnapshotOptions(TypedDict): - AutomatedSnapshotStartHour: Optional[int] + AutomatedSnapshotStartHour: int | None class VPCOptions(TypedDict): - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class NodeToNodeEncryptionOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class DomainEndpointOptions(TypedDict): - CustomEndpoint: Optional[str] - CustomEndpointCertificateArn: Optional[str] - CustomEndpointEnabled: Optional[bool] - EnforceHTTPS: Optional[bool] - TLSSecurityPolicy: Optional[str] + CustomEndpoint: str | None + CustomEndpointCertificateArn: str | None + CustomEndpointEnabled: bool | None + EnforceHTTPS: bool | None + TLSSecurityPolicy: str | None class CognitoOptions(TypedDict): - Enabled: Optional[bool] - IdentityPoolId: Optional[str] - RoleArn: Optional[str] - UserPoolId: Optional[str] + Enabled: bool | None + IdentityPoolId: str | None + RoleArn: str | None + UserPoolId: str | None class MasterUserOptions(TypedDict): - MasterUserARN: Optional[str] - MasterUserName: Optional[str] - MasterUserPassword: Optional[str] + MasterUserARN: str | None + MasterUserName: str | None + MasterUserPassword: str | None class AdvancedSecurityOptionsInput(TypedDict): - AnonymousAuthEnabled: Optional[bool] - Enabled: Optional[bool] - InternalUserDatabaseEnabled: Optional[bool] - MasterUserOptions: Optional[MasterUserOptions] + AnonymousAuthEnabled: bool | None + Enabled: bool | None + InternalUserDatabaseEnabled: bool | None + MasterUserOptions: MasterUserOptions | None class EBSOptions(TypedDict): - EBSEnabled: Optional[bool] - Iops: Optional[int] - VolumeSize: Optional[int] - VolumeType: Optional[str] + EBSEnabled: bool | None + Iops: int | None + VolumeSize: int | None + VolumeType: str | None class EncryptionAtRestOptions(TypedDict): - Enabled: Optional[bool] - KmsKeyId: Optional[str] + Enabled: bool | None + KmsKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py index c5f22fa0b816e..918a07ddb4f5a 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_elasticsearch_domain_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ElasticsearchDomainProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Elasticsearch::Domain" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.opensearch.resource_providers.aws_elasticsearch_domain import ( diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py index 96b8c60ec0b2b..e27139b836454 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,151 +15,151 @@ class OpenSearchServiceDomainProperties(TypedDict): - AccessPolicies: Optional[dict] - AdvancedOptions: Optional[dict] - AdvancedSecurityOptions: Optional[AdvancedSecurityOptionsInput] - Arn: Optional[str] - ClusterConfig: Optional[ClusterConfig] - CognitoOptions: Optional[CognitoOptions] - DomainArn: Optional[str] - DomainEndpoint: Optional[str] - DomainEndpointOptions: Optional[DomainEndpointOptions] - DomainEndpoints: Optional[dict] - DomainName: Optional[str] - EBSOptions: Optional[EBSOptions] - EncryptionAtRestOptions: Optional[EncryptionAtRestOptions] - EngineVersion: Optional[str] - Id: Optional[str] - LogPublishingOptions: Optional[dict] - NodeToNodeEncryptionOptions: Optional[NodeToNodeEncryptionOptions] - OffPeakWindowOptions: Optional[OffPeakWindowOptions] - ServiceSoftwareOptions: Optional[ServiceSoftwareOptions] - SnapshotOptions: Optional[SnapshotOptions] - SoftwareUpdateOptions: Optional[SoftwareUpdateOptions] - Tags: Optional[list[Tag]] - VPCOptions: Optional[VPCOptions] + AccessPolicies: dict | None + AdvancedOptions: dict | None + AdvancedSecurityOptions: AdvancedSecurityOptionsInput | None + Arn: str | None + ClusterConfig: ClusterConfig | None + CognitoOptions: CognitoOptions | None + DomainArn: str | None + DomainEndpoint: str | None + DomainEndpointOptions: DomainEndpointOptions | None + DomainEndpoints: dict | None + DomainName: str | None + EBSOptions: EBSOptions | None + EncryptionAtRestOptions: EncryptionAtRestOptions | None + EngineVersion: str | None + Id: str | None + LogPublishingOptions: dict | None + NodeToNodeEncryptionOptions: NodeToNodeEncryptionOptions | None + OffPeakWindowOptions: OffPeakWindowOptions | None + ServiceSoftwareOptions: ServiceSoftwareOptions | None + SnapshotOptions: SnapshotOptions | None + SoftwareUpdateOptions: SoftwareUpdateOptions | None + Tags: list[Tag] | None + VPCOptions: VPCOptions | None class ZoneAwarenessConfig(TypedDict): - AvailabilityZoneCount: Optional[int] + AvailabilityZoneCount: int | None class ClusterConfig(TypedDict): - DedicatedMasterCount: Optional[int] - DedicatedMasterEnabled: Optional[bool] - DedicatedMasterType: Optional[str] - InstanceCount: Optional[int] - InstanceType: Optional[str] - WarmCount: Optional[int] - WarmEnabled: Optional[bool] - WarmType: Optional[str] - ZoneAwarenessConfig: Optional[ZoneAwarenessConfig] - ZoneAwarenessEnabled: Optional[bool] + DedicatedMasterCount: int | None + DedicatedMasterEnabled: bool | None + DedicatedMasterType: str | None + InstanceCount: int | None + InstanceType: str | None + WarmCount: int | None + WarmEnabled: bool | None + WarmType: str | None + ZoneAwarenessConfig: ZoneAwarenessConfig | None + ZoneAwarenessEnabled: bool | None class SnapshotOptions(TypedDict): - AutomatedSnapshotStartHour: Optional[int] + AutomatedSnapshotStartHour: int | None class VPCOptions(TypedDict): - SecurityGroupIds: Optional[list[str]] - SubnetIds: Optional[list[str]] + SecurityGroupIds: list[str] | None + SubnetIds: list[str] | None class NodeToNodeEncryptionOptions(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class DomainEndpointOptions(TypedDict): - CustomEndpoint: Optional[str] - CustomEndpointCertificateArn: Optional[str] - CustomEndpointEnabled: Optional[bool] - EnforceHTTPS: Optional[bool] - TLSSecurityPolicy: Optional[str] + CustomEndpoint: str | None + CustomEndpointCertificateArn: str | None + CustomEndpointEnabled: bool | None + EnforceHTTPS: bool | None + TLSSecurityPolicy: str | None class CognitoOptions(TypedDict): - Enabled: Optional[bool] - IdentityPoolId: Optional[str] - RoleArn: Optional[str] - UserPoolId: Optional[str] + Enabled: bool | None + IdentityPoolId: str | None + RoleArn: str | None + UserPoolId: str | None class MasterUserOptions(TypedDict): - MasterUserARN: Optional[str] - MasterUserName: Optional[str] - MasterUserPassword: Optional[str] + MasterUserARN: str | None + MasterUserName: str | None + MasterUserPassword: str | None class Idp(TypedDict): - EntityId: Optional[str] - MetadataContent: Optional[str] + EntityId: str | None + MetadataContent: str | None class SAMLOptions(TypedDict): - Enabled: Optional[bool] - Idp: Optional[Idp] - MasterBackendRole: Optional[str] - MasterUserName: Optional[str] - RolesKey: Optional[str] - SessionTimeoutMinutes: Optional[int] - SubjectKey: Optional[str] + Enabled: bool | None + Idp: Idp | None + MasterBackendRole: str | None + MasterUserName: str | None + RolesKey: str | None + SessionTimeoutMinutes: int | None + SubjectKey: str | None class AdvancedSecurityOptionsInput(TypedDict): - AnonymousAuthDisableDate: Optional[str] - AnonymousAuthEnabled: Optional[bool] - Enabled: Optional[bool] - InternalUserDatabaseEnabled: Optional[bool] - MasterUserOptions: Optional[MasterUserOptions] - SAMLOptions: Optional[SAMLOptions] + AnonymousAuthDisableDate: str | None + AnonymousAuthEnabled: bool | None + Enabled: bool | None + InternalUserDatabaseEnabled: bool | None + MasterUserOptions: MasterUserOptions | None + SAMLOptions: SAMLOptions | None class EBSOptions(TypedDict): - EBSEnabled: Optional[bool] - Iops: Optional[int] - Throughput: Optional[int] - VolumeSize: Optional[int] - VolumeType: Optional[str] + EBSEnabled: bool | None + Iops: int | None + Throughput: int | None + VolumeSize: int | None + VolumeType: str | None class EncryptionAtRestOptions(TypedDict): - Enabled: Optional[bool] - KmsKeyId: Optional[str] + Enabled: bool | None + KmsKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ServiceSoftwareOptions(TypedDict): - AutomatedUpdateDate: Optional[str] - Cancellable: Optional[bool] - CurrentVersion: Optional[str] - Description: Optional[str] - NewVersion: Optional[str] - OptionalDeployment: Optional[bool] - UpdateAvailable: Optional[bool] - UpdateStatus: Optional[str] + AutomatedUpdateDate: str | None + Cancellable: bool | None + CurrentVersion: str | None + Description: str | None + NewVersion: str | None + OptionalDeployment: bool | None + UpdateAvailable: bool | None + UpdateStatus: str | None class WindowStartTime(TypedDict): - Hours: Optional[int] - Minutes: Optional[int] + Hours: int | None + Minutes: int | None class OffPeakWindow(TypedDict): - WindowStartTime: Optional[WindowStartTime] + WindowStartTime: WindowStartTime | None class OffPeakWindowOptions(TypedDict): - Enabled: Optional[bool] - OffPeakWindow: Optional[OffPeakWindow] + Enabled: bool | None + OffPeakWindow: OffPeakWindow | None class SoftwareUpdateOptions(TypedDict): - AutoSoftwareUpdateEnabled: Optional[bool] + AutoSoftwareUpdateEnabled: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py index 029076b1aefa8..b07e149ff6db2 100644 --- a/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py +++ b/localstack-core/localstack/services/opensearch/resource_providers/aws_opensearchservice_domain_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class OpenSearchServiceDomainProviderPlugin(CloudFormationResourceProviderPlugin name = "AWS::OpenSearchService::Domain" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.opensearch.resource_providers.aws_opensearchservice_domain import ( diff --git a/localstack-core/localstack/services/opensearch/versions.py b/localstack-core/localstack/services/opensearch/versions.py index 205b9b33d5202..a5f8a3d5a7179 100644 --- a/localstack-core/localstack/services/opensearch/versions.py +++ b/localstack-core/localstack/services/opensearch/versions.py @@ -6,8 +6,6 @@ """ -from typing import Dict - import semver from localstack.aws.api.opensearch import CompatibleVersionsMap, EngineType @@ -301,7 +299,7 @@ def get_download_url(install_version: str, engine_type: EngineType) -> str: return _es_url(install_version) -def fetch_latest_versions() -> Dict[str, str]: # pragma: no cover +def fetch_latest_versions() -> dict[str, str]: # pragma: no cover """ Fetches from the opensearch git repository tags the latest patch versions for a minor version and returns a dictionary where the key corresponds to the minor version, and the value to the patch version. Run this once in a diff --git a/localstack-core/localstack/services/plugins.py b/localstack-core/localstack/services/plugins.py index fbd75a53f0ca7..ca7b77d9f027f 100644 --- a/localstack-core/localstack/services/plugins.py +++ b/localstack-core/localstack/services/plugins.py @@ -3,9 +3,10 @@ import logging import threading from collections import defaultdict +from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor from enum import Enum -from typing import Callable, Dict, List, Optional, Protocol, Tuple +from typing import Protocol from plux import Plugin, PluginLifecycleListener, PluginManager, PluginSpec @@ -190,7 +191,7 @@ class ServiceContainer: service: Service state: ServiceState lock: threading.RLock - errors: List[Exception] + errors: list[Exception] def __init__(self, service: Service, state=ServiceState.UNKNOWN): self.service = service @@ -236,13 +237,13 @@ def stop(self): class ServiceManager: def __init__(self) -> None: super().__init__() - self._services: Dict[str, ServiceContainer] = {} + self._services: dict[str, ServiceContainer] = {} self._mutex = threading.RLock() - def get_service_container(self, name: str) -> Optional[ServiceContainer]: + def get_service_container(self, name: str) -> ServiceContainer | None: return self._services.get(name) - def get_service(self, name: str) -> Optional[Service]: + def get_service(self, name: str) -> Service | None: container = self.get_service_container(name) return container.service if container else None @@ -252,7 +253,7 @@ def add_service(self, service: Service) -> bool: return True - def list_available(self) -> List[str]: + def list_available(self) -> list[str]: return list(self._services.keys()) def exists(self, name: str) -> bool: @@ -268,11 +269,11 @@ def check(self, name: str) -> bool: def check_all(self): return any(self.check(service_name) for service_name in self.list_available()) - def get_state(self, name: str) -> Optional[ServiceState]: + def get_state(self, name: str) -> ServiceState | None: container = self.get_service_container(name) return container.state if container else None - def get_states(self) -> Dict[str, ServiceState]: + def get_states(self) -> dict[str, ServiceState]: return {name: self.get_state(name) for name in self.list_available()} @log_duration() @@ -397,13 +398,13 @@ class ServicePluginErrorCollector(PluginLifecycleListener): A PluginLifecycleListener that collects errors related to service plugins. """ - errors: Dict[Tuple[str, str], Exception] # keys are: (api, provider) + errors: dict[tuple[str, str], Exception] # keys are: (api, provider) - def __init__(self, errors: Dict[str, Exception] = None) -> None: + def __init__(self, errors: dict[str, Exception] = None) -> None: super().__init__() self.errors = errors or {} - def get_key(self, plugin_name) -> Tuple[str, str]: + def get_key(self, plugin_name) -> tuple[str, str]: # the convention is :, currently we don't really expose the provider # TODO: faulty plugin names would break this return tuple(plugin_name.split(":", maxsplit=1)) @@ -446,7 +447,7 @@ def __init__( self.provider_config = provider_config or config.SERVICE_PROVIDER_CONFIG # locks used to make sure plugin loading is thread safe - will be cleared after single use - self._plugin_load_locks: Dict[str, threading.RLock] = SynchronizedDefaultDict( + self._plugin_load_locks: dict[str, threading.RLock] = SynchronizedDefaultDict( threading.RLock ) @@ -469,7 +470,7 @@ def get_default_provider(self) -> str: # TODO make the abstraction clearer, to provide better information if service is available versus discoverable # especially important when considering pro services - def list_available(self) -> List[str]: + def list_available(self) -> list[str]: """ List all available services, which have an available, configured provider @@ -482,8 +483,8 @@ def list_available(self) -> List[str]: ] def _get_loaded_service_containers( - self, services: Optional[List[str]] = None - ) -> List[ServiceContainer]: + self, services: list[str] | None = None + ) -> list[ServiceContainer]: """ Returns all the available service containers. :param services: the list of services to restrict the search to. If empty or NULL then service containers for @@ -495,7 +496,7 @@ def _get_loaded_service_containers( c for s in services if (c := super(ServicePluginManager, self).get_service_container(s)) ] - def list_loaded_services(self) -> List[str]: + def list_loaded_services(self) -> list[str]: """ Lists all the services which have a provider that has been initialized @@ -506,7 +507,7 @@ def list_loaded_services(self) -> List[str]: for service_container in self._get_loaded_service_containers() ] - def list_active_services(self) -> List[str]: + def list_active_services(self) -> list[str]: """ Lists all services that have an initialised provider and are currently running. @@ -521,7 +522,7 @@ def list_active_services(self) -> List[str]: def exists(self, name: str) -> bool: return name in self.list_available() - def get_state(self, name: str) -> Optional[ServiceState]: + def get_state(self, name: str) -> ServiceState | None: if name in self._services: # ServiceContainer exists, which means the plugin has been loaded return super().get_state(name) @@ -537,7 +538,7 @@ def get_state(self, name: str) -> Optional[ServiceState]: return ServiceState.AVAILABLE if is_api_enabled(name) else ServiceState.DISABLED - def get_service_container(self, name: str) -> Optional[ServiceContainer]: + def get_service_container(self, name: str) -> ServiceContainer | None: if container := self._services.get(name): return container @@ -565,7 +566,7 @@ def get_service_container(self, name: str) -> Optional[ServiceContainer]: return self._services.get(name) @property - def api_provider_specs(self) -> Dict[str, List[str]]: + def api_provider_specs(self) -> dict[str, list[str]]: """ Returns all provider names within the service plugin namespace and parses their name according to the convention, that is ":". The result is a dictionary that maps api => List[str (name of a provider)]. @@ -579,7 +580,7 @@ def api_provider_specs(self) -> Dict[str, List[str]]: return self._api_provider_specs @log_duration() - def _load_service_plugin(self, name: str) -> Optional[ServicePlugin]: + def _load_service_plugin(self, name: str) -> ServicePlugin | None: providers = self.api_provider_specs.get(name) if not providers: # no providers for this api @@ -608,7 +609,7 @@ def _load_service_plugin(self, name: str) -> Optional[ServicePlugin]: return plugin @log_duration() - def _resolve_api_provider_specs(self) -> Dict[str, List[str]]: + def _resolve_api_provider_specs(self) -> dict[str, list[str]]: result = defaultdict(list) for spec in self.plugin_manager.list_plugin_specs(): @@ -619,7 +620,7 @@ def _resolve_api_provider_specs(self) -> Dict[str, List[str]]: return result - def apis_with_provider(self, provider: str) -> List[str]: + def apis_with_provider(self, provider: str) -> list[str]: """ Lists all apis where a given provider exists for. :param provider: Name of the provider @@ -631,7 +632,7 @@ def apis_with_provider(self, provider: str) -> List[str]: apis.append(api) return apis - def _stop_services(self, service_containers: List[ServiceContainer]) -> None: + def _stop_services(self, service_containers: list[ServiceContainer]) -> None: """ Atomically attempts to stop all given 'ServiceState.STARTING' and 'ServiceState.RUNNING' services. :param service_containers: the list of service containers to be stopped. @@ -642,7 +643,7 @@ def _stop_services(self, service_containers: List[ServiceContainer]) -> None: if service_container.state in target_service_states: service_container.stop() - def stop_services(self, services: List[str] = None): + def stop_services(self, services: list[str] = None): """ Stops services for this service manager, if they are currently active. Will not stop services not already started or in and error state. diff --git a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py index 629a7ca7a5b2e..ad3e6d3860d49 100644 --- a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py +++ b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,71 +14,71 @@ class RedshiftClusterProperties(TypedDict): - ClusterType: Optional[str] - DBName: Optional[str] - MasterUserPassword: Optional[str] - MasterUsername: Optional[str] - NodeType: Optional[str] - AllowVersionUpgrade: Optional[bool] - AquaConfigurationStatus: Optional[str] - AutomatedSnapshotRetentionPeriod: Optional[int] - AvailabilityZone: Optional[str] - AvailabilityZoneRelocation: Optional[bool] - AvailabilityZoneRelocationStatus: Optional[str] - Classic: Optional[bool] - ClusterIdentifier: Optional[str] - ClusterParameterGroupName: Optional[str] - ClusterSecurityGroups: Optional[list[str]] - ClusterSubnetGroupName: Optional[str] - ClusterVersion: Optional[str] - DeferMaintenance: Optional[bool] - DeferMaintenanceDuration: Optional[int] - DeferMaintenanceEndTime: Optional[str] - DeferMaintenanceIdentifier: Optional[str] - DeferMaintenanceStartTime: Optional[str] - DestinationRegion: Optional[str] - ElasticIp: Optional[str] - Encrypted: Optional[bool] - Endpoint: Optional[Endpoint] - EnhancedVpcRouting: Optional[bool] - HsmClientCertificateIdentifier: Optional[str] - HsmConfigurationIdentifier: Optional[str] - IamRoles: Optional[list[str]] - Id: Optional[str] - KmsKeyId: Optional[str] - LoggingProperties: Optional[LoggingProperties] - MaintenanceTrackName: Optional[str] - ManualSnapshotRetentionPeriod: Optional[int] - NumberOfNodes: Optional[int] - OwnerAccount: Optional[str] - Port: Optional[int] - PreferredMaintenanceWindow: Optional[str] - PubliclyAccessible: Optional[bool] - ResourceAction: Optional[str] - RevisionTarget: Optional[str] - RotateEncryptionKey: Optional[bool] - SnapshotClusterIdentifier: Optional[str] - SnapshotCopyGrantName: Optional[str] - SnapshotCopyManual: Optional[bool] - SnapshotCopyRetentionPeriod: Optional[int] - SnapshotIdentifier: Optional[str] - Tags: Optional[list[Tag]] - VpcSecurityGroupIds: Optional[list[str]] + ClusterType: str | None + DBName: str | None + MasterUserPassword: str | None + MasterUsername: str | None + NodeType: str | None + AllowVersionUpgrade: bool | None + AquaConfigurationStatus: str | None + AutomatedSnapshotRetentionPeriod: int | None + AvailabilityZone: str | None + AvailabilityZoneRelocation: bool | None + AvailabilityZoneRelocationStatus: str | None + Classic: bool | None + ClusterIdentifier: str | None + ClusterParameterGroupName: str | None + ClusterSecurityGroups: list[str] | None + ClusterSubnetGroupName: str | None + ClusterVersion: str | None + DeferMaintenance: bool | None + DeferMaintenanceDuration: int | None + DeferMaintenanceEndTime: str | None + DeferMaintenanceIdentifier: str | None + DeferMaintenanceStartTime: str | None + DestinationRegion: str | None + ElasticIp: str | None + Encrypted: bool | None + Endpoint: Endpoint | None + EnhancedVpcRouting: bool | None + HsmClientCertificateIdentifier: str | None + HsmConfigurationIdentifier: str | None + IamRoles: list[str] | None + Id: str | None + KmsKeyId: str | None + LoggingProperties: LoggingProperties | None + MaintenanceTrackName: str | None + ManualSnapshotRetentionPeriod: int | None + NumberOfNodes: int | None + OwnerAccount: str | None + Port: int | None + PreferredMaintenanceWindow: str | None + PubliclyAccessible: bool | None + ResourceAction: str | None + RevisionTarget: str | None + RotateEncryptionKey: bool | None + SnapshotClusterIdentifier: str | None + SnapshotCopyGrantName: str | None + SnapshotCopyManual: bool | None + SnapshotCopyRetentionPeriod: int | None + SnapshotIdentifier: str | None + Tags: list[Tag] | None + VpcSecurityGroupIds: list[str] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class LoggingProperties(TypedDict): - BucketName: Optional[str] - S3KeyPrefix: Optional[str] + BucketName: str | None + S3KeyPrefix: str | None class Endpoint(TypedDict): - Address: Optional[str] - Port: Optional[str] + Address: str | None + Port: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py index 742fa8c2c1c39..0afc2929dc385 100644 --- a/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py +++ b/localstack-core/localstack/services/redshift/resource_providers/aws_redshift_cluster_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class RedshiftClusterProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Redshift::Cluster" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.redshift.resource_providers.aws_redshift_cluster import ( diff --git a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py index 0105de3b2233f..981babca150ff 100644 --- a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py +++ b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,44 +15,44 @@ class ResourceGroupsGroupProperties(TypedDict): - Name: Optional[str] - Arn: Optional[str] - Configuration: Optional[list[ConfigurationItem]] - Description: Optional[str] - ResourceQuery: Optional[ResourceQuery] - Resources: Optional[list[str]] - Tags: Optional[list[Tag]] + Name: str | None + Arn: str | None + Configuration: list[ConfigurationItem] | None + Description: str | None + ResourceQuery: ResourceQuery | None + Resources: list[str] | None + Tags: list[Tag] | None class TagFilter(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class Query(TypedDict): - ResourceTypeFilters: Optional[list[str]] - StackIdentifier: Optional[str] - TagFilters: Optional[list[TagFilter]] + ResourceTypeFilters: list[str] | None + StackIdentifier: str | None + TagFilters: list[TagFilter] | None class ResourceQuery(TypedDict): - Query: Optional[Query] - Type: Optional[str] + Query: Query | None + Type: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class ConfigurationParameter(TypedDict): - Name: Optional[str] - Values: Optional[list[str]] + Name: str | None + Values: list[str] | None class ConfigurationItem(TypedDict): - Parameters: Optional[list[ConfigurationParameter]] - Type: Optional[str] + Parameters: list[ConfigurationParameter] | None + Type: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py index 99e589abd9722..5f2a825667068 100644 --- a/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py +++ b/localstack-core/localstack/services/resource_groups/resource_providers/aws_resourcegroups_group_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class ResourceGroupsGroupProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::ResourceGroups::Group" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.resource_groups.resource_providers.aws_resourcegroups_group import ( diff --git a/localstack-core/localstack/services/route53/models.py b/localstack-core/localstack/services/route53/models.py index 9bfd65e612d33..3d96a9c223100 100644 --- a/localstack-core/localstack/services/route53/models.py +++ b/localstack-core/localstack/services/route53/models.py @@ -1,12 +1,10 @@ -from typing import Dict - from localstack.aws.api.route53 import DelegationSet from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute class Route53Store(BaseStore): # maps delegation set ID to reusable delegation set details - reusable_delegation_sets: Dict[str, DelegationSet] = LocalAttribute(default=dict) + reusable_delegation_sets: dict[str, DelegationSet] = LocalAttribute(default=dict) route53_stores = AccountRegionBundle("route53", Route53Store) diff --git a/localstack-core/localstack/services/route53/provider.py b/localstack-core/localstack/services/route53/provider.py index cdd3650adf274..58078805f9f1f 100644 --- a/localstack-core/localstack/services/route53/provider.py +++ b/localstack-core/localstack/services/route53/provider.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Optional import moto.route53.models as route53_models from botocore.exceptions import ClientError @@ -79,7 +78,7 @@ def get_change(self, context: RequestContext, id: ResourceId, **kwargs) -> GetCh def get_health_check( self, context: RequestContext, health_check_id: HealthCheckId, **kwargs ) -> GetHealthCheckResponse: - health_check: Optional[route53_models.HealthCheck] = route53_backends[context.account_id][ + health_check: route53_models.HealthCheck | None = route53_backends[context.account_id][ context.partition ].health_checks.get(health_check_id, None) if not health_check: diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py index ddd156b7e638f..440f7323d2142 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class Route53HealthCheckProperties(TypedDict): - HealthCheckConfig: Optional[dict] - HealthCheckId: Optional[str] - HealthCheckTags: Optional[list[HealthCheckTag]] + HealthCheckConfig: dict | None + HealthCheckId: str | None + HealthCheckTags: list[HealthCheckTag] | None class HealthCheckTag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py index 7a8e244561cf8..9db349c9853a1 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_healthcheck_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class Route53HealthCheckProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Route53::HealthCheck" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.route53.resource_providers.aws_route53_healthcheck import ( diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py index c3d0e3866e14c..e2497adfe09c1 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Optional, TypedDict +from typing import TYPE_CHECKING, TypedDict if TYPE_CHECKING: from mypy_boto3_route53 import Route53Client @@ -17,40 +17,40 @@ class Route53RecordSetProperties(TypedDict): - Name: Optional[str] - Type: Optional[str] - AliasTarget: Optional[AliasTarget] - CidrRoutingConfig: Optional[CidrRoutingConfig] - Comment: Optional[str] - Failover: Optional[str] - GeoLocation: Optional[GeoLocation] - HealthCheckId: Optional[str] - HostedZoneId: Optional[str] - HostedZoneName: Optional[str] - Id: Optional[str] - MultiValueAnswer: Optional[bool] - Region: Optional[str] - ResourceRecords: Optional[list[str]] - SetIdentifier: Optional[str] - TTL: Optional[str] - Weight: Optional[int] + Name: str | None + Type: str | None + AliasTarget: AliasTarget | None + CidrRoutingConfig: CidrRoutingConfig | None + Comment: str | None + Failover: str | None + GeoLocation: GeoLocation | None + HealthCheckId: str | None + HostedZoneId: str | None + HostedZoneName: str | None + Id: str | None + MultiValueAnswer: bool | None + Region: str | None + ResourceRecords: list[str] | None + SetIdentifier: str | None + TTL: str | None + Weight: int | None class AliasTarget(TypedDict): - DNSName: Optional[str] - HostedZoneId: Optional[str] - EvaluateTargetHealth: Optional[bool] + DNSName: str | None + HostedZoneId: str | None + EvaluateTargetHealth: bool | None class CidrRoutingConfig(TypedDict): - CollectionId: Optional[str] - LocationName: Optional[str] + CollectionId: str | None + LocationName: str | None class GeoLocation(TypedDict): - ContinentCode: Optional[str] - CountryCode: Optional[str] - SubdivisionCode: Optional[str] + ContinentCode: str | None + CountryCode: str | None + SubdivisionCode: str | None REPEATED_INVOCATION = "repeated_invocation" @@ -138,7 +138,7 @@ def create( resource_model=model, ) - def get_hosted_zone_id_from_name(self, hosted_zone_name: str, client: "Route53Client"): + def get_hosted_zone_id_from_name(self, hosted_zone_name: str, client: Route53Client): if not hosted_zone_name: raise Exception("Either HostedZoneId or HostedZoneName must be present.") diff --git a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py index bdecf6996b78d..0f890fef915a7 100644 --- a/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py +++ b/localstack-core/localstack/services/route53/resource_providers/aws_route53_recordset_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class Route53RecordSetProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Route53::RecordSet" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.route53.resource_providers.aws_route53_recordset import ( diff --git a/localstack-core/localstack/services/route53resolver/models.py b/localstack-core/localstack/services/route53resolver/models.py index 57c5436bab568..b29a7b8bf4236 100644 --- a/localstack-core/localstack/services/route53resolver/models.py +++ b/localstack-core/localstack/services/route53resolver/models.py @@ -1,5 +1,3 @@ -from typing import Dict - import localstack.services.route53resolver.utils from localstack.aws.api.route53resolver import ( FirewallConfig, @@ -18,16 +16,16 @@ class Route53ResolverStore(BaseStore): - firewall_configs: Dict[str, FirewallConfig] = LocalAttribute(default=dict) - firewall_domain_lists: Dict[str, FirewallDomainList] = LocalAttribute(default=dict) - firewall_domains: Dict[str, FirewallDomains] = LocalAttribute(default=dict) - firewall_rules: Dict[str, FirewallRule] = LocalAttribute(default=dict) - firewall_rule_groups: Dict[str, FirewallRuleGroup] = LocalAttribute(default=dict) - firewall_rule_group_associations: Dict[str, FirewallRuleGroupAssociation] = LocalAttribute( + firewall_configs: dict[str, FirewallConfig] = LocalAttribute(default=dict) + firewall_domain_lists: dict[str, FirewallDomainList] = LocalAttribute(default=dict) + firewall_domains: dict[str, FirewallDomains] = LocalAttribute(default=dict) + firewall_rules: dict[str, FirewallRule] = LocalAttribute(default=dict) + firewall_rule_groups: dict[str, FirewallRuleGroup] = LocalAttribute(default=dict) + firewall_rule_group_associations: dict[str, FirewallRuleGroupAssociation] = LocalAttribute( default=dict ) - resolver_query_log_configs: Dict[str, ResolverQueryLogConfig] = LocalAttribute(default=dict) - resolver_query_log_config_associations: Dict[str, ResolverQueryLogConfigAssociation] = ( + resolver_query_log_configs: dict[str, ResolverQueryLogConfig] = LocalAttribute(default=dict) + resolver_query_log_config_associations: dict[str, ResolverQueryLogConfigAssociation] = ( LocalAttribute(default=dict) ) diff --git a/localstack-core/localstack/services/route53resolver/provider.py b/localstack-core/localstack/services/route53resolver/provider.py index e002748d9aa17..7df4ade11cb25 100644 --- a/localstack-core/localstack/services/route53resolver/provider.py +++ b/localstack-core/localstack/services/route53resolver/provider.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from moto.route53resolver.models import Route53ResolverBackend as MotoRoute53ResolverBackend from moto.route53resolver.models import route53resolver_backends @@ -135,8 +135,8 @@ def create_firewall_rule_group( ShareStatus="NOT_SHARED", StatusMessage="Created Firewall Rule Group", CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), ) store.firewall_rule_groups[firewall_rule_group_id] = firewall_rule_group store.firewall_rules[firewall_rule_group_id] = {} @@ -202,8 +202,8 @@ def create_firewall_domain_list( StatusMessage="Created Firewall Domain List", ManagedOwnerName=context.account_id, CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), ) store.firewall_domain_lists[id] = firewall_domain_list route53resolver_backends[context.account_id][context.region].tagger.tag_resource( @@ -283,7 +283,7 @@ def update_firewall_domains( store.firewall_domains[firewall_domain_list_id] = domains firewall_domain_list["StatusMessage"] = "Finished domain list update" - firewall_domain_list["ModificationTime"] = datetime.now(timezone.utc).isoformat() + firewall_domain_list["ModificationTime"] = datetime.now(UTC).isoformat() return UpdateFirewallDomainsResponse( Id=firewall_domain_list.get("Id"), Name=firewall_domain_list.get("Name"), @@ -340,8 +340,8 @@ def create_firewall_rule( BlockOverrideDnsType=block_override_dns_type, BlockOverrideTtl=block_override_ttl, CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), FirewallDomainRedirectionAction=firewall_domain_redirection_action, Qtype=qtype, ) @@ -492,8 +492,8 @@ def associate_firewall_rule_group( Status="COMPLETE", StatusMessage="Creating Firewall Rule Group Association", CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), - ModificationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), + ModificationTime=datetime.now(UTC).isoformat(), ) store.firewall_rule_group_associations[id] = firewall_rule_group_association route53resolver_backends[context.account_id][context.region].tagger.tag_resource( @@ -579,7 +579,7 @@ def create_resolver_query_log_config( ShareStatus="NOT_SHARED", DestinationArn=destination_arn, CreatorRequestId=creator_request_id, - CreationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), ) store.resolver_query_log_configs[id] = resolver_query_log_config route53resolver_backends[context.account_id][context.region].tagger.tag_resource( @@ -667,7 +667,7 @@ def associate_resolver_query_log_config( Status="ACTIVE", Error="NONE", ErrorMessage="", - CreationTime=datetime.now(timezone.utc).isoformat(), + CreationTime=datetime.now(UTC).isoformat(), ) ) diff --git a/localstack-core/localstack/services/s3/codec.py b/localstack-core/localstack/services/s3/codec.py index 9d1b3167ccda8..98917156627aa 100644 --- a/localstack-core/localstack/services/s3/codec.py +++ b/localstack-core/localstack/services/s3/codec.py @@ -1,5 +1,5 @@ import io -from typing import IO, Any, Optional +from typing import IO, Any class AwsChunkedDecoder(io.RawIOBase): @@ -15,7 +15,7 @@ def readable(self): return True def __init__( - self, stream: IO[bytes], decoded_content_length: int, s3_object: Optional[Any] = None + self, stream: IO[bytes], decoded_content_length: int, s3_object: Any | None = None ): self._stream = stream diff --git a/localstack-core/localstack/services/s3/cors.py b/localstack-core/localstack/services/s3/cors.py index 9051f3679c6ca..13fb4a55ac662 100644 --- a/localstack-core/localstack/services/s3/cors.py +++ b/localstack-core/localstack/services/s3/cors.py @@ -1,6 +1,6 @@ import logging import re -from typing import Optional, Protocol, Tuple +from typing import Protocol from werkzeug.datastructures import Headers @@ -58,7 +58,7 @@ def __init__(self, bucket_cors_index: BucketCorsIndex): def __call__(self, chain: HandlerChain, context: RequestContext, response: Response): self.handle_cors(chain, context, response) - def pre_parse_s3_request(self, request: Request) -> Tuple[bool, Optional[str]]: + def pre_parse_s3_request(self, request: Request) -> tuple[bool, str | None]: """ Parse the request to try to determine if it's directed towards S3. It tries to match on host, then check if the targeted bucket exists. If we could not determine it was a s3 request from the host, but the first @@ -224,7 +224,7 @@ def stop_options_chain(): def invalidate_cache(self): self.bucket_cors_index.invalidate() - def match_rules(self, request: Request, rules: CORSRules) -> Optional[CORSRule]: + def match_rules(self, request: Request, rules: CORSRules) -> CORSRule | None: """ Try to match the request to the bucket rules. How to match rules: - The request's Origin header must match AllowedOrigin elements. @@ -243,7 +243,7 @@ def match_rules(self, request: Request, rules: CORSRules) -> Optional[CORSRule]: return matched_rule @staticmethod - def _match_rule(rule: CORSRule, method: str, headers: Headers) -> Optional[CORSRule]: + def _match_rule(rule: CORSRule, method: str, headers: Headers) -> CORSRule | None: """ Check if the request method and headers matches the given CORS rule. :param rule: CORSRule: a CORS Rule from the bucket diff --git a/localstack-core/localstack/services/s3/models.py b/localstack-core/localstack/services/s3/models.py index 6246d394dad33..507572bbf4aed 100644 --- a/localstack-core/localstack/services/s3/models.py +++ b/localstack-core/localstack/services/s3/models.py @@ -4,7 +4,7 @@ from collections import defaultdict from datetime import datetime from secrets import token_urlsafe -from typing import Literal, NamedTuple, Optional, Union +from typing import Literal, NamedTuple, Union from zoneinfo import ZoneInfo from localstack.aws.api import CommonServiceException @@ -105,25 +105,25 @@ class S3Bucket: multiparts: dict[MultipartUploadId, "S3Multipart"] objects: Union["KeyStore", "VersionedKeyStore"] versioning_status: BucketVersioningStatus | None - lifecycle_rules: Optional[LifecycleRules] - transition_default_minimum_object_size: Optional[TransitionDefaultMinimumObjectSize] - policy: Optional[Policy] - website_configuration: Optional[WebsiteConfiguration] + lifecycle_rules: LifecycleRules | None + transition_default_minimum_object_size: TransitionDefaultMinimumObjectSize | None + policy: Policy | None + website_configuration: WebsiteConfiguration | None acl: AccessControlPolicy - cors_rules: Optional[CORSConfiguration] + cors_rules: CORSConfiguration | None logging: LoggingEnabled notification_configuration: NotificationConfiguration payer: Payer - encryption_rule: Optional[ServerSideEncryptionRule] - public_access_block: Optional[PublicAccessBlockConfiguration] - accelerate_status: Optional[BucketAccelerateStatus] + encryption_rule: ServerSideEncryptionRule | None + public_access_block: PublicAccessBlockConfiguration | None + accelerate_status: BucketAccelerateStatus | None object_lock_enabled: bool object_ownership: ObjectOwnership intelligent_tiering_configurations: dict[IntelligentTieringId, IntelligentTieringConfiguration] analytics_configurations: dict[AnalyticsId, AnalyticsConfiguration] inventory_configurations: dict[InventoryId, InventoryConfiguration] metric_configurations: dict[MetricsId, MetricsConfiguration] - object_lock_default_retention: Optional[DefaultRetention] + object_lock_default_retention: DefaultRetention | None replication: ReplicationConfiguration owner: Owner @@ -256,58 +256,58 @@ def get_object( class S3Object: key: ObjectKey - version_id: Optional[ObjectVersionId] + version_id: ObjectVersionId | None bucket: BucketName - owner: Optional[Owner] - size: Optional[Size] - etag: Optional[ETag] + owner: Owner | None + size: Size | None + etag: ETag | None user_metadata: Metadata system_metadata: Metadata last_modified: datetime - expires: Optional[datetime] - expiration: Optional[Expiration] # right now, this is stored in the provider cache + expires: datetime | None + expiration: Expiration | None # right now, this is stored in the provider cache storage_class: StorageClass | ObjectStorageClass - encryption: Optional[ServerSideEncryption] # inherit bucket - kms_key_id: Optional[SSEKMSKeyId] # inherit bucket - bucket_key_enabled: Optional[bool] # inherit bucket - sse_key_hash: Optional[SSECustomerKeyMD5] + encryption: ServerSideEncryption | None # inherit bucket + kms_key_id: SSEKMSKeyId | None # inherit bucket + bucket_key_enabled: bool | None # inherit bucket + sse_key_hash: SSECustomerKeyMD5 | None checksum_algorithm: ChecksumAlgorithm checksum_value: str checksum_type: ChecksumType - lock_mode: Optional[ObjectLockMode | ObjectLockRetentionMode] - lock_legal_status: Optional[ObjectLockLegalHoldStatus] - lock_until: Optional[datetime] - website_redirect_location: Optional[WebsiteRedirectLocation] - acl: Optional[AccessControlPolicy] + lock_mode: ObjectLockMode | ObjectLockRetentionMode | None + lock_legal_status: ObjectLockLegalHoldStatus | None + lock_until: datetime | None + website_redirect_location: WebsiteRedirectLocation | None + acl: AccessControlPolicy | None is_current: bool - parts: Optional[dict[int, InternalObjectPart]] - restore: Optional[Restore] + parts: dict[int, InternalObjectPart] | None + restore: Restore | None internal_last_modified: int def __init__( self, key: ObjectKey, - etag: Optional[ETag] = None, - size: Optional[int] = None, - version_id: Optional[ObjectVersionId] = None, - user_metadata: Optional[Metadata] = None, - system_metadata: Optional[Metadata] = None, + etag: ETag | None = None, + size: int | None = None, + version_id: ObjectVersionId | None = None, + user_metadata: Metadata | None = None, + system_metadata: Metadata | None = None, storage_class: StorageClass = StorageClass.STANDARD, - expires: Optional[datetime] = None, - expiration: Optional[Expiration] = None, - checksum_algorithm: Optional[ChecksumAlgorithm] = None, - checksum_value: Optional[str] = None, - checksum_type: Optional[ChecksumType] = ChecksumType.FULL_OBJECT, - encryption: Optional[ServerSideEncryption] = None, - kms_key_id: Optional[SSEKMSKeyId] = None, - sse_key_hash: Optional[SSECustomerKeyMD5] = None, + expires: datetime | None = None, + expiration: Expiration | None = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + checksum_value: str | None = None, + checksum_type: ChecksumType | None = ChecksumType.FULL_OBJECT, + encryption: ServerSideEncryption | None = None, + kms_key_id: SSEKMSKeyId | None = None, + sse_key_hash: SSECustomerKeyMD5 | None = None, bucket_key_enabled: bool = False, - lock_mode: Optional[ObjectLockMode | ObjectLockRetentionMode] = None, - lock_legal_status: Optional[ObjectLockLegalHoldStatus] = None, - lock_until: Optional[datetime] = None, - website_redirect_location: Optional[WebsiteRedirectLocation] = None, - acl: Optional[AccessControlPolicy] = None, # TODO - owner: Optional[Owner] = None, + lock_mode: ObjectLockMode | ObjectLockRetentionMode | None = None, + lock_legal_status: ObjectLockLegalHoldStatus | None = None, + lock_until: datetime | None = None, + website_redirect_location: WebsiteRedirectLocation | None = None, + acl: AccessControlPolicy | None = None, # TODO + owner: Owner | None = None, ): self.key = key self.user_metadata = ( @@ -406,19 +406,19 @@ def is_locked(*args, **kwargs) -> bool: # TODO: could use dataclass, validate after models are set class S3Part: part_number: PartNumber - etag: Optional[ETag] + etag: ETag | None last_modified: datetime - size: Optional[int] - checksum_algorithm: Optional[ChecksumAlgorithm] - checksum_value: Optional[str] + size: int | None + checksum_algorithm: ChecksumAlgorithm | None + checksum_value: str | None def __init__( self, part_number: PartNumber, size: int = None, etag: ETag = None, - checksum_algorithm: Optional[ChecksumAlgorithm] = None, - checksum_value: Optional[str] = None, + checksum_algorithm: ChecksumAlgorithm | None = None, + checksum_value: str | None = None, ): self.last_modified = datetime.now(tz=_gmt_zone_info) self.part_number = part_number @@ -436,8 +436,8 @@ class S3Multipart: parts: dict[PartNumber, S3Part] object: S3Object upload_id: MultipartUploadId - checksum_value: Optional[str] - checksum_type: Optional[ChecksumType] + checksum_value: str | None + checksum_type: ChecksumType | None checksum_algorithm: ChecksumAlgorithm initiated: datetime precondition: bool @@ -446,25 +446,25 @@ def __init__( self, key: ObjectKey, storage_class: StorageClass | ObjectStorageClass = StorageClass.STANDARD, - expires: Optional[datetime] = None, - expiration: Optional[datetime] = None, # come from lifecycle - checksum_algorithm: Optional[ChecksumAlgorithm] = None, - checksum_type: Optional[ChecksumType] = None, - encryption: Optional[ServerSideEncryption] = None, # inherit bucket - kms_key_id: Optional[SSEKMSKeyId] = None, # inherit bucket + expires: datetime | None = None, + expiration: datetime | None = None, # come from lifecycle + checksum_algorithm: ChecksumAlgorithm | None = None, + checksum_type: ChecksumType | None = None, + encryption: ServerSideEncryption | None = None, # inherit bucket + kms_key_id: SSEKMSKeyId | None = None, # inherit bucket bucket_key_enabled: bool = False, # inherit bucket - sse_key_hash: Optional[SSECustomerKeyMD5] = None, - lock_mode: Optional[ObjectLockMode] = None, - lock_legal_status: Optional[ObjectLockLegalHoldStatus] = None, - lock_until: Optional[datetime] = None, - website_redirect_location: Optional[WebsiteRedirectLocation] = None, - acl: Optional[AccessControlPolicy] = None, # TODO - user_metadata: Optional[Metadata] = None, - system_metadata: Optional[Metadata] = None, - initiator: Optional[Owner] = None, - tagging: Optional[dict[str, str]] = None, - owner: Optional[Owner] = None, - precondition: Optional[bool] = None, + sse_key_hash: SSECustomerKeyMD5 | None = None, + lock_mode: ObjectLockMode | None = None, + lock_legal_status: ObjectLockLegalHoldStatus | None = None, + lock_until: datetime | None = None, + website_redirect_location: WebsiteRedirectLocation | None = None, + acl: AccessControlPolicy | None = None, # TODO + user_metadata: Metadata | None = None, + system_metadata: Metadata | None = None, + initiator: Owner | None = None, + tagging: dict[str, str] | None = None, + owner: Owner | None = None, + precondition: bool | None = None, ): self.id = token_urlsafe(96) # MultipartUploadId is 128 characters long self.initiated = datetime.now(tz=_gmt_zone_info) diff --git a/localstack-core/localstack/services/s3/notifications.py b/localstack-core/localstack/services/s3/notifications.py index 48ece2ab9e788..3f48923db2c6e 100644 --- a/localstack-core/localstack/services/s3/notifications.py +++ b/localstack-core/localstack/services/s3/notifications.py @@ -6,7 +6,7 @@ import re from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple, TypedDict, Union +from typing import TypedDict from urllib.parse import quote from botocore.exceptions import ClientError @@ -64,8 +64,8 @@ class S3NotificationContent(TypedDict): s3SchemaVersion: str configurationId: NotificationId - bucket: Dict[str, str] # todo - object: Dict[str, Union[str, int]] # todo + bucket: dict[str, str] # todo + object: dict[str, str | int] # todo class EventRecord(TypedDict): @@ -74,14 +74,14 @@ class EventRecord(TypedDict): awsRegion: str eventTime: str eventName: str - userIdentity: Dict[str, str] - requestParameters: Dict[str, str] - responseElements: Dict[str, str] + userIdentity: dict[str, str] + requestParameters: dict[str, str] + responseElements: dict[str, str] s3: S3NotificationContent class Notification(TypedDict): - Records: List[EventRecord] + Records: list[EventRecord] @dataclass @@ -101,7 +101,7 @@ class S3EventNotificationContext: key_etag: str key_version_id: str key_expiry: datetime.datetime - key_storage_class: Optional[StorageClass] + key_storage_class: StorageClass | None @classmethod def from_request_context_native( @@ -109,7 +109,7 @@ def from_request_context_native( request_context: RequestContext, s3_bucket: S3Bucket, s3_object: S3Object | S3DeleteMarker, - ) -> "S3EventNotificationContext": + ) -> S3EventNotificationContext: """ Create an S3EventNotificationContext from a RequestContext. The key is not always present in the request context depending on the event type. In that case, we can use @@ -165,7 +165,7 @@ class BucketVerificationContext: request_id: str bucket_name: str region: str - configuration: Dict + configuration: dict skip_destination_validation: bool @@ -185,7 +185,7 @@ def _matching_event(events: EventList, event_name: str) -> bool: def _matching_filter( - notification_filter: Optional[NotificationConfigurationFilter], key_name: str + notification_filter: NotificationConfigurationFilter | None, key_name: str ) -> bool: """ See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-filtering.html @@ -213,11 +213,11 @@ def _matching_filter( class BaseNotifier: service_name: str - def notify(self, ctx: S3EventNotificationContext, config: Dict): + def notify(self, ctx: S3EventNotificationContext, config: dict): raise NotImplementedError @staticmethod - def should_notify(ctx: S3EventNotificationContext, config: Dict) -> bool: + def should_notify(ctx: S3EventNotificationContext, config: dict) -> bool: """ Helper method checking if the event should be notified to the configured notifiers from the configuration :param ctx: S3EventNotificationContext @@ -229,12 +229,12 @@ def should_notify(ctx: S3EventNotificationContext, config: Dict) -> bool: ) @staticmethod - def _get_arn_value_and_name(notifier_configuration: Dict) -> Tuple[str, str]: + def _get_arn_value_and_name(notifier_configuration: dict) -> tuple[str, str]: raise NotImplementedError def validate_configuration_for_notifier( self, - configurations: List[Dict], + configurations: list[dict], skip_destination_validation: bool, context: RequestContext, bucket_name: str, @@ -381,7 +381,7 @@ class SqsNotifier(BaseNotifier): service_name = "sqs" @staticmethod - def _get_arn_value_and_name(queue_configuration: QueueConfiguration) -> Tuple[QueueArn, str]: + def _get_arn_value_and_name(queue_configuration: QueueConfiguration) -> tuple[QueueArn, str]: return queue_configuration.get("QueueArn", ""), "QueueArn" def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationContext) -> None: @@ -549,7 +549,7 @@ class LambdaNotifier(BaseNotifier): @staticmethod def _get_arn_value_and_name( lambda_configuration: LambdaFunctionConfiguration, - ) -> Tuple[LambdaFunctionArn, str]: + ) -> tuple[LambdaFunctionArn, str]: return lambda_configuration.get("LambdaFunctionArn", ""), "LambdaFunctionArn" def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationContext) -> None: @@ -698,14 +698,14 @@ def _get_event_payload( return entry @staticmethod - def should_notify(ctx: S3EventNotificationContext, config: Dict) -> bool: + def should_notify(ctx: S3EventNotificationContext, config: dict) -> bool: # Events are always passed to EventBridge, you can route the event in EventBridge # See https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html return True def validate_configuration_for_notifier( self, - configurations: List[Dict], + configurations: list[dict], skip_destination_validation: bool, context: RequestContext, bucket_name: str, diff --git a/localstack-core/localstack/services/s3/presigned_url.py b/localstack-core/localstack/services/s3/presigned_url.py index e696e82e2c2dc..734798ef83fc2 100644 --- a/localstack-core/localstack/services/s3/presigned_url.py +++ b/localstack-core/localstack/services/s3/presigned_url.py @@ -6,8 +6,9 @@ import re import time from collections import namedtuple +from collections.abc import Mapping from functools import cache, cached_property -from typing import Mapping, Optional, TypedDict +from typing import TypedDict from urllib import parse as urlparse from botocore.auth import HmacV1QueryAuth, S3SigV4QueryAuth @@ -105,7 +106,7 @@ class NotValidSigV4SignatureContext(TypedDict): canonical_request: str -FindSigV4Result = tuple["S3SigV4SignatureContext", Optional[NotValidSigV4SignatureContext]] +FindSigV4Result = tuple["S3SigV4SignatureContext", NotValidSigV4SignatureContext | None] class HmacV1QueryAuthValidation(HmacV1QueryAuth): @@ -258,7 +259,7 @@ def get_credentials_from_parameters(parameters: dict, region: str) -> PreSignedC @cache -def get_secret_access_key_from_access_key_id(access_key_id: str, region: str) -> Optional[str]: +def get_secret_access_key_from_access_key_id(access_key_id: str, region: str) -> str | None: """ We need to retrieve the internal secret access key in order to also sign the request on our side to validate it For now, we need to access Moto internals, as they are no public APIs to retrieve it for obvious reasons. @@ -472,7 +473,7 @@ def validate_presigned_url_s3v4(context: RequestContext) -> None: x_amz_expires = int(query_parameters["X-Amz-Expires"]) x_amz_expires_dt = datetime.timedelta(seconds=x_amz_expires) expiration_time = x_amz_date + x_amz_expires_dt - expiration_time = expiration_time.replace(tzinfo=datetime.timezone.utc) + expiration_time = expiration_time.replace(tzinfo=datetime.UTC) if is_expired(expiration_time): if config.S3_SKIP_SIGNATURE_VALIDATION: @@ -905,7 +906,7 @@ def _parse_policy_expiration_date(expiration_string: str) -> datetime.datetime: dt = datetime.datetime.strptime(expiration_string, POLICY_EXPIRATION_FORMAT2) # both date formats assume a UTC timezone ('Z' suffix), but it's not parsed as tzinfo into the datetime object - dt = dt.replace(tzinfo=datetime.timezone.utc) + dt = dt.replace(tzinfo=datetime.UTC) return dt diff --git a/localstack-core/localstack/services/s3/provider.py b/localstack-core/localstack/services/s3/provider.py index bc679c196a966..b9bb2a0b55709 100644 --- a/localstack-core/localstack/services/s3/provider.py +++ b/localstack-core/localstack/services/s3/provider.py @@ -8,7 +8,7 @@ from inspect import signature from io import BytesIO from operator import itemgetter -from typing import IO, Optional, Union +from typing import IO from urllib import parse as urlparse from zoneinfo import ZoneInfo @@ -2545,7 +2545,7 @@ def upload_part_copy( source_range = request.get("CopySourceRange") # TODO implement copy source IF - range_data: Optional[ObjectRange] = None + range_data: ObjectRange | None = None if source_range: range_data = parse_copy_source_range_header(source_range, src_s3_object.size) @@ -4820,14 +4820,12 @@ def get_part_range(s3_object: S3Object, part_number: PartNumber) -> ObjectRange: def get_acl_headers_from_request( - request: Union[ - PutObjectRequest, - CreateMultipartUploadRequest, - CopyObjectRequest, - CreateBucketRequest, - PutBucketAclRequest, - PutObjectAclRequest, - ], + request: PutObjectRequest + | CreateMultipartUploadRequest + | CopyObjectRequest + | CreateBucketRequest + | PutBucketAclRequest + | PutObjectAclRequest, ) -> list[tuple[str, str]]: permission_keys = [ "GrantFullControl", @@ -4845,7 +4843,7 @@ def get_acl_headers_from_request( def get_access_control_policy_from_acl_request( - request: Union[PutBucketAclRequest, PutObjectAclRequest], + request: PutBucketAclRequest | PutObjectAclRequest, owner: Owner, request_body: bytes, ) -> AccessControlPolicy: @@ -4894,9 +4892,10 @@ def get_access_control_policy_from_acl_request( def get_access_control_policy_for_new_resource_request( - request: Union[ - PutObjectRequest, CreateMultipartUploadRequest, CopyObjectRequest, CreateBucketRequest - ], + request: PutObjectRequest + | CreateMultipartUploadRequest + | CopyObjectRequest + | CreateBucketRequest, owner: Owner, ) -> AccessControlPolicy: # TODO: this is basic ACL, not taking into account Bucket settings. Revisit once we really implement ACLs. diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py index de1573274b2b8..3737dec8f863f 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket.py @@ -3,7 +3,7 @@ import re from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -22,356 +22,356 @@ class S3BucketProperties(TypedDict): - AccelerateConfiguration: Optional[AccelerateConfiguration] - AccessControl: Optional[str] - AnalyticsConfigurations: Optional[list[AnalyticsConfiguration]] - Arn: Optional[str] - BucketEncryption: Optional[BucketEncryption] - BucketName: Optional[str] - CorsConfiguration: Optional[CorsConfiguration] - DomainName: Optional[str] - DualStackDomainName: Optional[str] - IntelligentTieringConfigurations: Optional[list[IntelligentTieringConfiguration]] - InventoryConfigurations: Optional[list[InventoryConfiguration]] - LifecycleConfiguration: Optional[LifecycleConfiguration] - LoggingConfiguration: Optional[LoggingConfiguration] - MetricsConfigurations: Optional[list[MetricsConfiguration]] - NotificationConfiguration: Optional[NotificationConfiguration] - ObjectLockConfiguration: Optional[ObjectLockConfiguration] - ObjectLockEnabled: Optional[bool] - OwnershipControls: Optional[OwnershipControls] - PublicAccessBlockConfiguration: Optional[PublicAccessBlockConfiguration] - RegionalDomainName: Optional[str] - ReplicationConfiguration: Optional[ReplicationConfiguration] - Tags: Optional[list[Tag]] - VersioningConfiguration: Optional[VersioningConfiguration] - WebsiteConfiguration: Optional[WebsiteConfiguration] - WebsiteURL: Optional[str] + AccelerateConfiguration: AccelerateConfiguration | None + AccessControl: str | None + AnalyticsConfigurations: list[AnalyticsConfiguration] | None + Arn: str | None + BucketEncryption: BucketEncryption | None + BucketName: str | None + CorsConfiguration: CorsConfiguration | None + DomainName: str | None + DualStackDomainName: str | None + IntelligentTieringConfigurations: list[IntelligentTieringConfiguration] | None + InventoryConfigurations: list[InventoryConfiguration] | None + LifecycleConfiguration: LifecycleConfiguration | None + LoggingConfiguration: LoggingConfiguration | None + MetricsConfigurations: list[MetricsConfiguration] | None + NotificationConfiguration: NotificationConfiguration | None + ObjectLockConfiguration: ObjectLockConfiguration | None + ObjectLockEnabled: bool | None + OwnershipControls: OwnershipControls | None + PublicAccessBlockConfiguration: PublicAccessBlockConfiguration | None + RegionalDomainName: str | None + ReplicationConfiguration: ReplicationConfiguration | None + Tags: list[Tag] | None + VersioningConfiguration: VersioningConfiguration | None + WebsiteConfiguration: WebsiteConfiguration | None + WebsiteURL: str | None class AccelerateConfiguration(TypedDict): - AccelerationStatus: Optional[str] + AccelerationStatus: str | None class TagFilter(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class Destination(TypedDict): - BucketArn: Optional[str] - Format: Optional[str] - BucketAccountId: Optional[str] - Prefix: Optional[str] + BucketArn: str | None + Format: str | None + BucketAccountId: str | None + Prefix: str | None class DataExport(TypedDict): - Destination: Optional[Destination] - OutputSchemaVersion: Optional[str] + Destination: Destination | None + OutputSchemaVersion: str | None class StorageClassAnalysis(TypedDict): - DataExport: Optional[DataExport] + DataExport: DataExport | None class AnalyticsConfiguration(TypedDict): - Id: Optional[str] - StorageClassAnalysis: Optional[StorageClassAnalysis] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Id: str | None + StorageClassAnalysis: StorageClassAnalysis | None + Prefix: str | None + TagFilters: list[TagFilter] | None class ServerSideEncryptionByDefault(TypedDict): - SSEAlgorithm: Optional[str] - KMSMasterKeyID: Optional[str] + SSEAlgorithm: str | None + KMSMasterKeyID: str | None class ServerSideEncryptionRule(TypedDict): - BucketKeyEnabled: Optional[bool] - ServerSideEncryptionByDefault: Optional[ServerSideEncryptionByDefault] + BucketKeyEnabled: bool | None + ServerSideEncryptionByDefault: ServerSideEncryptionByDefault | None class BucketEncryption(TypedDict): - ServerSideEncryptionConfiguration: Optional[list[ServerSideEncryptionRule]] + ServerSideEncryptionConfiguration: list[ServerSideEncryptionRule] | None class CorsRule(TypedDict): - AllowedMethods: Optional[list[str]] - AllowedOrigins: Optional[list[str]] - AllowedHeaders: Optional[list[str]] - ExposedHeaders: Optional[list[str]] - Id: Optional[str] - MaxAge: Optional[int] + AllowedMethods: list[str] | None + AllowedOrigins: list[str] | None + AllowedHeaders: list[str] | None + ExposedHeaders: list[str] | None + Id: str | None + MaxAge: int | None class CorsConfiguration(TypedDict): - CorsRules: Optional[list[CorsRule]] + CorsRules: list[CorsRule] | None class Tiering(TypedDict): - AccessTier: Optional[str] - Days: Optional[int] + AccessTier: str | None + Days: int | None class IntelligentTieringConfiguration(TypedDict): - Id: Optional[str] - Status: Optional[str] - Tierings: Optional[list[Tiering]] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Id: str | None + Status: str | None + Tierings: list[Tiering] | None + Prefix: str | None + TagFilters: list[TagFilter] | None class InventoryConfiguration(TypedDict): - Destination: Optional[Destination] - Enabled: Optional[bool] - Id: Optional[str] - IncludedObjectVersions: Optional[str] - ScheduleFrequency: Optional[str] - OptionalFields: Optional[list[str]] - Prefix: Optional[str] + Destination: Destination | None + Enabled: bool | None + Id: str | None + IncludedObjectVersions: str | None + ScheduleFrequency: str | None + OptionalFields: list[str] | None + Prefix: str | None class AbortIncompleteMultipartUpload(TypedDict): - DaysAfterInitiation: Optional[int] + DaysAfterInitiation: int | None class NoncurrentVersionExpiration(TypedDict): - NoncurrentDays: Optional[int] - NewerNoncurrentVersions: Optional[int] + NoncurrentDays: int | None + NewerNoncurrentVersions: int | None class NoncurrentVersionTransition(TypedDict): - StorageClass: Optional[str] - TransitionInDays: Optional[int] - NewerNoncurrentVersions: Optional[int] + StorageClass: str | None + TransitionInDays: int | None + NewerNoncurrentVersions: int | None class Transition(TypedDict): - StorageClass: Optional[str] - TransitionDate: Optional[str] - TransitionInDays: Optional[int] + StorageClass: str | None + TransitionDate: str | None + TransitionInDays: int | None class Rule(TypedDict): - Status: Optional[str] - AbortIncompleteMultipartUpload: Optional[AbortIncompleteMultipartUpload] - ExpirationDate: Optional[str] - ExpirationInDays: Optional[int] - ExpiredObjectDeleteMarker: Optional[bool] - Id: Optional[str] - NoncurrentVersionExpiration: Optional[NoncurrentVersionExpiration] - NoncurrentVersionExpirationInDays: Optional[int] - NoncurrentVersionTransition: Optional[NoncurrentVersionTransition] - NoncurrentVersionTransitions: Optional[list[NoncurrentVersionTransition]] - ObjectSizeGreaterThan: Optional[str] - ObjectSizeLessThan: Optional[str] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] - Transition: Optional[Transition] - Transitions: Optional[list[Transition]] + Status: str | None + AbortIncompleteMultipartUpload: AbortIncompleteMultipartUpload | None + ExpirationDate: str | None + ExpirationInDays: int | None + ExpiredObjectDeleteMarker: bool | None + Id: str | None + NoncurrentVersionExpiration: NoncurrentVersionExpiration | None + NoncurrentVersionExpirationInDays: int | None + NoncurrentVersionTransition: NoncurrentVersionTransition | None + NoncurrentVersionTransitions: list[NoncurrentVersionTransition] | None + ObjectSizeGreaterThan: str | None + ObjectSizeLessThan: str | None + Prefix: str | None + TagFilters: list[TagFilter] | None + Transition: Transition | None + Transitions: list[Transition] | None class LifecycleConfiguration(TypedDict): - Rules: Optional[list[Rule]] + Rules: list[Rule] | None class LoggingConfiguration(TypedDict): - DestinationBucketName: Optional[str] - LogFilePrefix: Optional[str] + DestinationBucketName: str | None + LogFilePrefix: str | None class MetricsConfiguration(TypedDict): - Id: Optional[str] - AccessPointArn: Optional[str] - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Id: str | None + AccessPointArn: str | None + Prefix: str | None + TagFilters: list[TagFilter] | None class EventBridgeConfiguration(TypedDict): - EventBridgeEnabled: Optional[bool] + EventBridgeEnabled: bool | None class FilterRule(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class S3KeyFilter(TypedDict): - Rules: Optional[list[FilterRule]] + Rules: list[FilterRule] | None class NotificationFilter(TypedDict): - S3Key: Optional[S3KeyFilter] + S3Key: S3KeyFilter | None class LambdaConfiguration(TypedDict): - Event: Optional[str] - Function: Optional[str] - Filter: Optional[NotificationFilter] + Event: str | None + Function: str | None + Filter: NotificationFilter | None class QueueConfiguration(TypedDict): - Event: Optional[str] - Queue: Optional[str] - Filter: Optional[NotificationFilter] + Event: str | None + Queue: str | None + Filter: NotificationFilter | None class TopicConfiguration(TypedDict): - Event: Optional[str] - Topic: Optional[str] - Filter: Optional[NotificationFilter] + Event: str | None + Topic: str | None + Filter: NotificationFilter | None class NotificationConfiguration(TypedDict): - EventBridgeConfiguration: Optional[EventBridgeConfiguration] - LambdaConfigurations: Optional[list[LambdaConfiguration]] - QueueConfigurations: Optional[list[QueueConfiguration]] - TopicConfigurations: Optional[list[TopicConfiguration]] + EventBridgeConfiguration: EventBridgeConfiguration | None + LambdaConfigurations: list[LambdaConfiguration] | None + QueueConfigurations: list[QueueConfiguration] | None + TopicConfigurations: list[TopicConfiguration] | None class DefaultRetention(TypedDict): - Days: Optional[int] - Mode: Optional[str] - Years: Optional[int] + Days: int | None + Mode: str | None + Years: int | None class ObjectLockRule(TypedDict): - DefaultRetention: Optional[DefaultRetention] + DefaultRetention: DefaultRetention | None class ObjectLockConfiguration(TypedDict): - ObjectLockEnabled: Optional[str] - Rule: Optional[ObjectLockRule] + ObjectLockEnabled: str | None + Rule: ObjectLockRule | None class OwnershipControlsRule(TypedDict): - ObjectOwnership: Optional[str] + ObjectOwnership: str | None class OwnershipControls(TypedDict): - Rules: Optional[list[OwnershipControlsRule]] + Rules: list[OwnershipControlsRule] | None class PublicAccessBlockConfiguration(TypedDict): - BlockPublicAcls: Optional[bool] - BlockPublicPolicy: Optional[bool] - IgnorePublicAcls: Optional[bool] - RestrictPublicBuckets: Optional[bool] + BlockPublicAcls: bool | None + BlockPublicPolicy: bool | None + IgnorePublicAcls: bool | None + RestrictPublicBuckets: bool | None class DeleteMarkerReplication(TypedDict): - Status: Optional[str] + Status: str | None class AccessControlTranslation(TypedDict): - Owner: Optional[str] + Owner: str | None class EncryptionConfiguration(TypedDict): - ReplicaKmsKeyID: Optional[str] + ReplicaKmsKeyID: str | None class ReplicationTimeValue(TypedDict): - Minutes: Optional[int] + Minutes: int | None class Metrics(TypedDict): - Status: Optional[str] - EventThreshold: Optional[ReplicationTimeValue] + Status: str | None + EventThreshold: ReplicationTimeValue | None class ReplicationTime(TypedDict): - Status: Optional[str] - Time: Optional[ReplicationTimeValue] + Status: str | None + Time: ReplicationTimeValue | None class ReplicationDestination(TypedDict): - Bucket: Optional[str] - AccessControlTranslation: Optional[AccessControlTranslation] - Account: Optional[str] - EncryptionConfiguration: Optional[EncryptionConfiguration] - Metrics: Optional[Metrics] - ReplicationTime: Optional[ReplicationTime] - StorageClass: Optional[str] + Bucket: str | None + AccessControlTranslation: AccessControlTranslation | None + Account: str | None + EncryptionConfiguration: EncryptionConfiguration | None + Metrics: Metrics | None + ReplicationTime: ReplicationTime | None + StorageClass: str | None class ReplicationRuleAndOperator(TypedDict): - Prefix: Optional[str] - TagFilters: Optional[list[TagFilter]] + Prefix: str | None + TagFilters: list[TagFilter] | None class ReplicationRuleFilter(TypedDict): - And: Optional[ReplicationRuleAndOperator] - Prefix: Optional[str] - TagFilter: Optional[TagFilter] + And: ReplicationRuleAndOperator | None + Prefix: str | None + TagFilter: TagFilter | None class ReplicaModifications(TypedDict): - Status: Optional[str] + Status: str | None class SseKmsEncryptedObjects(TypedDict): - Status: Optional[str] + Status: str | None class SourceSelectionCriteria(TypedDict): - ReplicaModifications: Optional[ReplicaModifications] - SseKmsEncryptedObjects: Optional[SseKmsEncryptedObjects] + ReplicaModifications: ReplicaModifications | None + SseKmsEncryptedObjects: SseKmsEncryptedObjects | None class ReplicationRule(TypedDict): - Destination: Optional[ReplicationDestination] - Status: Optional[str] - DeleteMarkerReplication: Optional[DeleteMarkerReplication] - Filter: Optional[ReplicationRuleFilter] - Id: Optional[str] - Prefix: Optional[str] - Priority: Optional[int] - SourceSelectionCriteria: Optional[SourceSelectionCriteria] + Destination: ReplicationDestination | None + Status: str | None + DeleteMarkerReplication: DeleteMarkerReplication | None + Filter: ReplicationRuleFilter | None + Id: str | None + Prefix: str | None + Priority: int | None + SourceSelectionCriteria: SourceSelectionCriteria | None class ReplicationConfiguration(TypedDict): - Role: Optional[str] - Rules: Optional[list[ReplicationRule]] + Role: str | None + Rules: list[ReplicationRule] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None class VersioningConfiguration(TypedDict): - Status: Optional[str] + Status: str | None class RedirectRule(TypedDict): - HostName: Optional[str] - HttpRedirectCode: Optional[str] - Protocol: Optional[str] - ReplaceKeyPrefixWith: Optional[str] - ReplaceKeyWith: Optional[str] + HostName: str | None + HttpRedirectCode: str | None + Protocol: str | None + ReplaceKeyPrefixWith: str | None + ReplaceKeyWith: str | None class RoutingRuleCondition(TypedDict): - HttpErrorCodeReturnedEquals: Optional[str] - KeyPrefixEquals: Optional[str] + HttpErrorCodeReturnedEquals: str | None + KeyPrefixEquals: str | None class RoutingRule(TypedDict): - RedirectRule: Optional[RedirectRule] - RoutingRuleCondition: Optional[RoutingRuleCondition] + RedirectRule: RedirectRule | None + RoutingRuleCondition: RoutingRuleCondition | None class RedirectAllRequestsTo(TypedDict): - HostName: Optional[str] - Protocol: Optional[str] + HostName: str | None + Protocol: str | None class WebsiteConfiguration(TypedDict): - ErrorDocument: Optional[str] - IndexDocument: Optional[str] - RedirectAllRequestsTo: Optional[RedirectAllRequestsTo] - RoutingRules: Optional[list[RoutingRule]] + ErrorDocument: str | None + IndexDocument: str | None + RedirectAllRequestsTo: RedirectAllRequestsTo | None + RoutingRules: list[RoutingRule] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py index d79e772ca7a65..c08b38facd9c3 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucket_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class S3BucketProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::S3::Bucket" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.s3.resource_providers.aws_s3_bucket import S3BucketProvider diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py index 78c5db3544efa..fa585d7f5fdec 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -17,9 +17,9 @@ class S3BucketPolicyProperties(TypedDict): - Bucket: Optional[str] - PolicyDocument: Optional[dict] - Id: Optional[str] + Bucket: str | None + PolicyDocument: dict | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py index 1589f69b38ad6..3d0f58bdd4e19 100644 --- a/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py +++ b/localstack-core/localstack/services/s3/resource_providers/aws_s3_bucketpolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class S3BucketPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::S3::BucketPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.s3.resource_providers.aws_s3_bucketpolicy import ( diff --git a/localstack-core/localstack/services/s3/storage/core.py b/localstack-core/localstack/services/s3/storage/core.py index d925f3cfc2b7e..b6962275c3e33 100644 --- a/localstack-core/localstack/services/s3/storage/core.py +++ b/localstack-core/localstack/services/s3/storage/core.py @@ -1,6 +1,7 @@ import abc +from collections.abc import Iterable, Iterator from io import RawIOBase -from typing import IO, Iterable, Iterator, Literal, Optional +from typing import IO, Literal from localstack.aws.api.s3 import BucketName, PartNumber from localstack.services.s3.models import S3Multipart, S3Object, S3Part @@ -108,7 +109,7 @@ def last_modified(self) -> int: @property @abc.abstractmethod - def checksum(self) -> Optional[str]: + def checksum(self) -> str | None: if not self.s3_object.checksum_algorithm: return None @@ -170,7 +171,7 @@ def copy_from_object( s3_part: S3Part, src_bucket: BucketName, src_s3_object: S3Object, - range_data: Optional[ObjectRange], + range_data: ObjectRange | None, ) -> None: pass diff --git a/localstack-core/localstack/services/s3/storage/ephemeral.py b/localstack-core/localstack/services/s3/storage/ephemeral.py index 64fc3440d7996..ebc21c61215b2 100644 --- a/localstack-core/localstack/services/s3/storage/ephemeral.py +++ b/localstack-core/localstack/services/s3/storage/ephemeral.py @@ -4,11 +4,12 @@ import threading import time from collections import defaultdict +from collections.abc import Iterator from io import BytesIO, UnsupportedOperation from shutil import rmtree from tempfile import SpooledTemporaryFile, mkdtemp from threading import RLock -from typing import IO, Iterator, Literal, Optional, TypedDict +from typing import IO, Literal, TypedDict from readerwriterlock import rwlock @@ -61,9 +62,9 @@ class EphemeralS3StoredObject(S3StoredObject): file: LockedSpooledTemporaryFile size: int _pos: int - etag: Optional[str] - checksum_hash: Optional[ChecksumHash] - _checksum: Optional[str] + etag: str | None + checksum_hash: ChecksumHash | None + _checksum: str | None _lock: rwlock.Lockable def __init__( @@ -202,7 +203,7 @@ def last_modified(self) -> int: return self.file.internal_last_modified @property - def checksum(self) -> Optional[str]: + def checksum(self) -> str | None: """ Return the object checksum base64 encoded, if the S3Object has a checksum algorithm. If the checksum hasn't been calculated, this method will iterate over the file again to recalculate it. @@ -323,7 +324,7 @@ def copy_from_object( s3_part: S3Part, src_bucket: BucketName, src_s3_object: S3Object, - range_data: Optional[ObjectRange], + range_data: ObjectRange | None, ) -> None: """ Create and add an EphemeralS3StoredObject to the Multipart collection, with an S3Object as input. This will diff --git a/localstack-core/localstack/services/s3/utils.py b/localstack-core/localstack/services/s3/utils.py index 8592de4712594..96375294d4b6b 100644 --- a/localstack-core/localstack/services/s3/utils.py +++ b/localstack-core/localstack/services/s3/utils.py @@ -9,7 +9,7 @@ import zlib from enum import StrEnum from secrets import token_bytes -from typing import Any, Dict, Literal, NamedTuple, Optional, Protocol, Tuple, Union +from typing import Any, Literal, NamedTuple, Protocol from urllib import parse as urlparser from zoneinfo import ZoneInfo @@ -143,7 +143,7 @@ def get_owner_for_account_id(account_id: str): def extract_bucket_key_version_id_from_copy_source( copy_source: CopySource, -) -> tuple[BucketName, ObjectKey, Optional[ObjectVersionId]]: +) -> tuple[BucketName, ObjectKey, ObjectVersionId | None]: """ Utility to parse bucket name, object key and optionally its versionId. It accepts the CopySource format: - ?versionId=, used for example in CopySource for CopyObject @@ -482,7 +482,7 @@ def is_valid_canonical_id(canonical_id: str) -> bool: return False -def uses_host_addressing(headers: Dict[str, str]) -> str | None: +def uses_host_addressing(headers: dict[str, str]) -> str | None: """ Determines if the request is targeting S3 with virtual host addressing :param headers: the request headers @@ -522,7 +522,7 @@ def forwarded_from_virtual_host_addressed_request(headers: dict[str, str]) -> bo def extract_bucket_name_and_key_from_headers_and_path( headers: dict[str, str], path: str -) -> tuple[Optional[str], Optional[str]]: +) -> tuple[str | None, str | None]: """ Extract the bucket name and the object key from a request headers and path. This works with both virtual host and path style requests. @@ -556,7 +556,7 @@ def normalize_bucket_name(bucket_name): return bucket_name -def get_bucket_and_key_from_s3_uri(s3_uri: str) -> Tuple[str, str]: +def get_bucket_and_key_from_s3_uri(s3_uri: str) -> tuple[str, str]: """ Extracts the bucket name and key from s3 uri """ @@ -564,7 +564,7 @@ def get_bucket_and_key_from_s3_uri(s3_uri: str) -> Tuple[str, str]: return output_bucket, output_key -def get_bucket_and_key_from_presign_url(presign_url: str) -> Tuple[str, str]: +def get_bucket_and_key_from_presign_url(presign_url: str) -> tuple[str, str]: """ Extracts the bucket name and key from s3 presign url """ @@ -575,7 +575,7 @@ def get_bucket_and_key_from_presign_url(presign_url: str) -> Tuple[str, str]: def _create_invalid_argument_exc( - message: Union[str, None], name: str, value: str, host_id: str = None + message: str | None, name: str, value: str, host_id: str = None ) -> InvalidArgument: ex = InvalidArgument(message) ex.ArgumentName = name @@ -589,7 +589,7 @@ def capitalize_header_name_from_snake_case(header_name: str) -> str: return "-".join([part.capitalize() for part in header_name.split("-")]) -def get_kms_key_arn(kms_key: str, account_id: str, bucket_region: str) -> Optional[str]: +def get_kms_key_arn(kms_key: str, account_id: str, bucket_region: str) -> str | None: """ In S3, the KMS key can be passed as a KeyId or a KeyArn. This method allows to always get the KeyArn from either. It can also validate if the key is in the same region, and raise an exception. @@ -762,7 +762,7 @@ def _match_lifecycle_filter( def parse_expiration_header( expiration_header: str, -) -> tuple[Optional[datetime.datetime], Optional[str]]: +) -> tuple[datetime.datetime | None, str | None]: try: header_values = dict( (p.strip('"') for p in v.split("=")) for v in expiration_header.split('", ') @@ -878,7 +878,7 @@ def get_retention_from_now(days: int = None, years: int = None) -> datetime.date def get_failed_precondition_copy_source( request: CopyObjectRequest, last_modified: datetime.datetime, etag: ETag -) -> Optional[str]: +) -> str | None: """ Validate if the source object LastModified and ETag matches a precondition, and if it does, return the failed precondition @@ -1019,7 +1019,7 @@ def create_redirect_for_post_request( return urlparser.urlunparse(newparts) -def parse_post_object_tagging_xml(tagging: str) -> Optional[dict]: +def parse_post_object_tagging_xml(tagging: str) -> dict | None: try: tag_set = {} tags = xmltodict.parse(tagging) diff --git a/localstack-core/localstack/services/s3/validation.py b/localstack-core/localstack/services/s3/validation.py index 884b9f6cd11ba..70bc8af38bbfa 100644 --- a/localstack-core/localstack/services/s3/validation.py +++ b/localstack-core/localstack/services/s3/validation.py @@ -173,7 +173,7 @@ def validate_acl_acp(acp: AccessControlPolicy) -> None: continue elif grant_type == GranteeType.CanonicalUser and not is_valid_canonical_id( - (grantee_id := grantee.get("ID", "")) + grantee_id := grantee.get("ID", "") ): ex = _create_invalid_argument_exc("Invalid id", "CanonicalUser/ID", grantee_id) raise ex diff --git a/localstack-core/localstack/services/s3/website_hosting.py b/localstack-core/localstack/services/s3/website_hosting.py index 141dc4e935105..c6e6aa3b83579 100644 --- a/localstack-core/localstack/services/s3/website_hosting.py +++ b/localstack-core/localstack/services/s3/website_hosting.py @@ -1,7 +1,7 @@ import logging import re +from collections.abc import Callable from functools import wraps -from typing import Callable, Dict, Optional, Union from urllib.parse import urlparse from werkzeug.datastructures import Headers @@ -32,8 +32,8 @@ class NoSuchKeyFromErrorDocument(NoSuchKey): code: str = "NoSuchKey" sender_fault: bool = False status_code: int = 404 - Key: Optional[ObjectKey] - ErrorDocumentKey: Optional[ObjectKey] + Key: ObjectKey | None + ErrorDocumentKey: ObjectKey | None class S3WebsiteHostingHandler: @@ -223,7 +223,7 @@ def _return_error_document( ) @staticmethod - def _get_response_headers_from_object(get_object_response: GetObjectOutput) -> Dict[str, str]: + def _get_response_headers_from_object(get_object_response: GetObjectOutput) -> dict[str, str]: """ Only return some headers from the S3 Object :param get_object_response: the response from S3.GetObject @@ -248,7 +248,7 @@ def _check_if_headers(headers: Headers, s3_object: GetObjectOutput) -> bool: @staticmethod def _find_matching_rule( routing_rules: RoutingRules, object_key: ObjectKey, error_code: int = None - ) -> Union[RoutingRule, None]: + ) -> RoutingRule | None: """ Iterate over the routing rules set in the configuration, and return the first that match the key name and/or the error code (in the 4XX range). diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py index adfc5316062ab..61b8e3e98fc3f 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,110 +14,110 @@ class SchedulerScheduleProperties(TypedDict): - FlexibleTimeWindow: Optional[FlexibleTimeWindow] - ScheduleExpression: Optional[str] - Target: Optional[Target] - Arn: Optional[str] - Description: Optional[str] - EndDate: Optional[str] - GroupName: Optional[str] - KmsKeyArn: Optional[str] - Name: Optional[str] - ScheduleExpressionTimezone: Optional[str] - StartDate: Optional[str] - State: Optional[str] + FlexibleTimeWindow: FlexibleTimeWindow | None + ScheduleExpression: str | None + Target: Target | None + Arn: str | None + Description: str | None + EndDate: str | None + GroupName: str | None + KmsKeyArn: str | None + Name: str | None + ScheduleExpressionTimezone: str | None + StartDate: str | None + State: str | None class FlexibleTimeWindow(TypedDict): - Mode: Optional[str] - MaximumWindowInMinutes: Optional[float] + Mode: str | None + MaximumWindowInMinutes: float | None class DeadLetterConfig(TypedDict): - Arn: Optional[str] + Arn: str | None class RetryPolicy(TypedDict): - MaximumEventAgeInSeconds: Optional[float] - MaximumRetryAttempts: Optional[float] + MaximumEventAgeInSeconds: float | None + MaximumRetryAttempts: float | None class AwsVpcConfiguration(TypedDict): - Subnets: Optional[list[str]] - AssignPublicIp: Optional[str] - SecurityGroups: Optional[list[str]] + Subnets: list[str] | None + AssignPublicIp: str | None + SecurityGroups: list[str] | None class NetworkConfiguration(TypedDict): - AwsvpcConfiguration: Optional[AwsVpcConfiguration] + AwsvpcConfiguration: AwsVpcConfiguration | None class CapacityProviderStrategyItem(TypedDict): - CapacityProvider: Optional[str] - Base: Optional[float] - Weight: Optional[float] + CapacityProvider: str | None + Base: float | None + Weight: float | None class PlacementConstraint(TypedDict): - Expression: Optional[str] - Type: Optional[str] + Expression: str | None + Type: str | None class PlacementStrategy(TypedDict): - Field: Optional[str] - Type: Optional[str] + Field: str | None + Type: str | None class EcsParameters(TypedDict): - TaskDefinitionArn: Optional[str] - CapacityProviderStrategy: Optional[list[CapacityProviderStrategyItem]] - EnableECSManagedTags: Optional[bool] - EnableExecuteCommand: Optional[bool] - Group: Optional[str] - LaunchType: Optional[str] - NetworkConfiguration: Optional[NetworkConfiguration] - PlacementConstraints: Optional[list[PlacementConstraint]] - PlacementStrategy: Optional[list[PlacementStrategy]] - PlatformVersion: Optional[str] - PropagateTags: Optional[str] - ReferenceId: Optional[str] - Tags: Optional[list[dict]] - TaskCount: Optional[float] + TaskDefinitionArn: str | None + CapacityProviderStrategy: list[CapacityProviderStrategyItem] | None + EnableECSManagedTags: bool | None + EnableExecuteCommand: bool | None + Group: str | None + LaunchType: str | None + NetworkConfiguration: NetworkConfiguration | None + PlacementConstraints: list[PlacementConstraint] | None + PlacementStrategy: list[PlacementStrategy] | None + PlatformVersion: str | None + PropagateTags: str | None + ReferenceId: str | None + Tags: list[dict] | None + TaskCount: float | None class EventBridgeParameters(TypedDict): - DetailType: Optional[str] - Source: Optional[str] + DetailType: str | None + Source: str | None class KinesisParameters(TypedDict): - PartitionKey: Optional[str] + PartitionKey: str | None class SageMakerPipelineParameter(TypedDict): - Name: Optional[str] - Value: Optional[str] + Name: str | None + Value: str | None class SageMakerPipelineParameters(TypedDict): - PipelineParameterList: Optional[list[SageMakerPipelineParameter]] + PipelineParameterList: list[SageMakerPipelineParameter] | None class SqsParameters(TypedDict): - MessageGroupId: Optional[str] + MessageGroupId: str | None class Target(TypedDict): - Arn: Optional[str] - RoleArn: Optional[str] - DeadLetterConfig: Optional[DeadLetterConfig] - EcsParameters: Optional[EcsParameters] - EventBridgeParameters: Optional[EventBridgeParameters] - Input: Optional[str] - KinesisParameters: Optional[KinesisParameters] - RetryPolicy: Optional[RetryPolicy] - SageMakerPipelineParameters: Optional[SageMakerPipelineParameters] - SqsParameters: Optional[SqsParameters] + Arn: str | None + RoleArn: str | None + DeadLetterConfig: DeadLetterConfig | None + EcsParameters: EcsParameters | None + EventBridgeParameters: EventBridgeParameters | None + Input: str | None + KinesisParameters: KinesisParameters | None + RetryPolicy: RetryPolicy | None + SageMakerPipelineParameters: SageMakerPipelineParameters | None + SqsParameters: SqsParameters | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py index b5fc742b5377b..57a87b915c7a9 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedule_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SchedulerScheduleProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::Scheduler::Schedule" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.scheduler.resource_providers.aws_scheduler_schedule import ( diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py index 913ce73707551..ace7a4aeb9767 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,17 +14,17 @@ class SchedulerScheduleGroupProperties(TypedDict): - Arn: Optional[str] - CreationDate: Optional[str] - LastModificationDate: Optional[str] - Name: Optional[str] - State: Optional[str] - Tags: Optional[list[Tag]] + Arn: str | None + CreationDate: str | None + LastModificationDate: str | None + Name: str | None + State: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py index 2f76e843976f7..a2c649960097e 100644 --- a/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py +++ b/localstack-core/localstack/services/scheduler/resource_providers/aws_scheduler_schedulegroup_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SchedulerScheduleGroupProviderPlugin(CloudFormationResourceProviderPlugin) name = "AWS::Scheduler::ScheduleGroup" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.scheduler.resource_providers.aws_scheduler_schedulegroup import ( diff --git a/localstack-core/localstack/services/secretsmanager/provider.py b/localstack-core/localstack/services/secretsmanager/provider.py index 5838732f2c4b0..e57a0d6b3d8f6 100644 --- a/localstack-core/localstack/services/secretsmanager/provider.py +++ b/localstack-core/localstack/services/secretsmanager/provider.py @@ -5,7 +5,7 @@ import logging import re import time -from typing import Any, Final, Optional, Union +from typing import Any, Final import moto.secretsmanager.exceptions as moto_exception from botocore.utils import InvalidArnException @@ -135,7 +135,7 @@ def _validate_secret_id(secret_id: SecretIdType) -> bool: return bool(re.match(r"^[A-Za-z0-9/_+=.@-]+\Z", secret_id)) @staticmethod - def _raise_if_invalid_secret_id(secret_id: Union[SecretIdType, NameType]): + def _raise_if_invalid_secret_id(secret_id: SecretIdType | NameType): # Patches moto's implementation for which secret_ids are not validated, by raising a ValidationException. # Skips this check if the secret_id provided appears to be an arn (starting with 'arn:'). if not re.match( @@ -149,12 +149,10 @@ def _raise_if_invalid_secret_id(secret_id: Union[SecretIdType, NameType]): @staticmethod def _raise_if_missing_client_req_token( - request: Union[ - CreateSecretRequest, - PutSecretValueRequest, - RotateSecretRequest, - UpdateSecretRequest, - ], + request: CreateSecretRequest + | PutSecretValueRequest + | RotateSecretRequest + | UpdateSecretRequest, ): if "ClientRequestToken" not in request: raise InvalidRequestException( @@ -201,8 +199,8 @@ def delete_secret( ) -> DeleteSecretResponse: secret_id: str = request["SecretId"] self._raise_if_invalid_secret_id(secret_id) - recovery_window_in_days: Optional[int] = request.get("RecoveryWindowInDays") - force_delete_without_recovery: Optional[bool] = request.get("ForceDeleteWithoutRecovery") + recovery_window_in_days: int | None = request.get("RecoveryWindowInDays") + force_delete_without_recovery: bool | None = request.get("ForceDeleteWithoutRecovery") backend = SecretsmanagerProvider.get_moto_backend_for_resource(secret_id, context) try: @@ -495,7 +493,7 @@ def moto_smb_get_secret_value(fn, self, secret_id, version_id, version_stage): def moto_smb_create_secret(fn, self, name, *args, **kwargs): # Creating a secret with a SecretId equal to one that is scheduled for # deletion should raise an 'InvalidRequestException'. - secret: Optional[FakeSecret] = self.secrets.get(name) + secret: FakeSecret | None = self.secrets.get(name) if secret is not None and secret.deleted_date is not None: raise InvalidRequestException(AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION) @@ -523,7 +521,7 @@ def moto_smb_list_secret_version_ids( secret = self.secrets[secret_id] # Patch: output format, report exact createdate instead of current time. - versions: list[SecretVersionsListEntry] = list() + versions: list[SecretVersionsListEntry] = [] for version_id, version in secret.versions.items(): version_stages = version["version_stages"] # Patch: include deprecated versions if include_deprecated is True. diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py index 53784023f67f5..4a99783e2d1ef 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,10 +15,10 @@ class SecretsManagerResourcePolicyProperties(TypedDict): - ResourcePolicy: Optional[dict] - SecretId: Optional[str] - BlockPublicPolicy: Optional[bool] - Id: Optional[str] + ResourcePolicy: dict | None + SecretId: str | None + BlockPublicPolicy: bool | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py index 1571bbfd89afc..19d7425eeedcb 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_resourcepolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerResourcePolicyProviderPlugin(CloudFormationResourceProviderP name = "AWS::SecretsManager::ResourcePolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_resourcepolicy import ( diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py index b838450d24a1d..76293c6b2b38f 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,32 +14,32 @@ class SecretsManagerRotationScheduleProperties(TypedDict): - SecretId: Optional[str] - HostedRotationLambda: Optional[HostedRotationLambda] - Id: Optional[str] - RotateImmediatelyOnUpdate: Optional[bool] - RotationLambdaARN: Optional[str] - RotationRules: Optional[RotationRules] + SecretId: str | None + HostedRotationLambda: HostedRotationLambda | None + Id: str | None + RotateImmediatelyOnUpdate: bool | None + RotationLambdaARN: str | None + RotationRules: RotationRules | None class RotationRules(TypedDict): - AutomaticallyAfterDays: Optional[int] - Duration: Optional[str] - ScheduleExpression: Optional[str] + AutomaticallyAfterDays: int | None + Duration: str | None + ScheduleExpression: str | None class HostedRotationLambda(TypedDict): - RotationType: Optional[str] - ExcludeCharacters: Optional[str] - KmsKeyArn: Optional[str] - MasterSecretArn: Optional[str] - MasterSecretKmsKeyArn: Optional[str] - RotationLambdaName: Optional[str] - Runtime: Optional[str] - SuperuserSecretArn: Optional[str] - SuperuserSecretKmsKeyArn: Optional[str] - VpcSecurityGroupIds: Optional[str] - VpcSubnetIds: Optional[str] + RotationType: str | None + ExcludeCharacters: str | None + KmsKeyArn: str | None + MasterSecretArn: str | None + MasterSecretKmsKeyArn: str | None + RotationLambdaName: str | None + Runtime: str | None + SuperuserSecretArn: str | None + SuperuserSecretKmsKeyArn: str | None + VpcSecurityGroupIds: str | None + VpcSubnetIds: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py index dd680bd788d1f..faef8a8c5698d 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_rotationschedule_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerRotationScheduleProviderPlugin(CloudFormationResourceProvide name = "AWS::SecretsManager::RotationSchedule" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_rotationschedule import ( diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py index d53dbd2e9aefe..4c1d3c31465a8 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret.py @@ -6,7 +6,7 @@ import random import string from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -20,37 +20,37 @@ class SecretsManagerSecretProperties(TypedDict): - Description: Optional[str] - GenerateSecretString: Optional[GenerateSecretString] - Id: Optional[str] - KmsKeyId: Optional[str] - Name: Optional[str] - ReplicaRegions: Optional[list[ReplicaRegion]] - SecretString: Optional[str] - Tags: Optional[list[Tag]] + Description: str | None + GenerateSecretString: GenerateSecretString | None + Id: str | None + KmsKeyId: str | None + Name: str | None + ReplicaRegions: list[ReplicaRegion] | None + SecretString: str | None + Tags: list[Tag] | None class GenerateSecretString(TypedDict): - ExcludeCharacters: Optional[str] - ExcludeLowercase: Optional[bool] - ExcludeNumbers: Optional[bool] - ExcludePunctuation: Optional[bool] - ExcludeUppercase: Optional[bool] - GenerateStringKey: Optional[str] - IncludeSpace: Optional[bool] - PasswordLength: Optional[int] - RequireEachIncludedType: Optional[bool] - SecretStringTemplate: Optional[str] + ExcludeCharacters: str | None + ExcludeLowercase: bool | None + ExcludeNumbers: bool | None + ExcludePunctuation: bool | None + ExcludeUppercase: bool | None + GenerateStringKey: str | None + IncludeSpace: bool | None + PasswordLength: int | None + RequireEachIncludedType: bool | None + SecretStringTemplate: str | None class ReplicaRegion(TypedDict): - Region: Optional[str] - KmsKeyId: Optional[str] + Region: str | None + KmsKeyId: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py index 4c85279d0d81f..8cd82e4510797 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secret_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerSecretProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SecretsManager::Secret" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_secret import ( diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py index 27f8682c0a51f..e58c7d049699f 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,10 +14,10 @@ class SecretsManagerSecretTargetAttachmentProperties(TypedDict): - SecretId: Optional[str] - TargetId: Optional[str] - TargetType: Optional[str] - Id: Optional[str] + SecretId: str | None + TargetId: str | None + TargetType: str | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py index f84e773ee3faf..5c7295f1f73ba 100644 --- a/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py +++ b/localstack-core/localstack/services/secretsmanager/resource_providers/aws_secretsmanager_secrettargetattachment_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SecretsManagerSecretTargetAttachmentProviderPlugin(CloudFormationResourceP name = "AWS::SecretsManager::SecretTargetAttachment" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.secretsmanager.resource_providers.aws_secretsmanager_secrettargetattachment import ( diff --git a/localstack-core/localstack/services/ses/models.py b/localstack-core/localstack/services/ses/models.py index 2560f872410da..666d5b2f08d06 100644 --- a/localstack-core/localstack/services/ses/models.py +++ b/localstack-core/localstack/services/ses/models.py @@ -1,3 +1,4 @@ +from enum import StrEnum from typing import TypedDict from localstack.aws.api.ses import Address, Destination, Subject, TemplateData, TemplateName @@ -8,7 +9,7 @@ class SentEmailBody(TypedDict): text_part: str -class SentEmail(TypedDict): +class SentEmail(TypedDict, total=False): Id: str Region: str Timestamp: str @@ -19,3 +20,9 @@ class SentEmail(TypedDict): Template: TemplateName TemplateData: TemplateData Body: SentEmailBody + + +class EmailType(StrEnum): + TEMPLATED = "templated" + RAW = "raw" + EMAIL = "email" diff --git a/localstack-core/localstack/services/ses/provider.py b/localstack-core/localstack/services/ses/provider.py index ca87c457c5818..942519cc610d1 100644 --- a/localstack-core/localstack/services/ses/provider.py +++ b/localstack-core/localstack/services/ses/provider.py @@ -4,8 +4,8 @@ import os import re from collections import defaultdict -from datetime import date, datetime, time, timezone -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from datetime import UTC, date, datetime, time +from typing import TYPE_CHECKING, Any from botocore.exceptions import ClientError from moto.ses import ses_backends @@ -26,10 +26,13 @@ DeleteConfigurationSetResponse, DeleteTemplateResponse, Destination, + Enabled, EventDestination, EventDestinationDoesNotExistException, EventDestinationName, + EventType, GetIdentityVerificationAttributesResponse, + Identity, IdentityList, IdentityVerificationAttributes, InvalidSNSDestinationException, @@ -40,12 +43,14 @@ MessageRejected, MessageTagList, NextToken, + NotificationType, RawMessage, ReceiptRuleSetName, SendEmailResponse, SendRawEmailResponse, SendTemplatedEmailResponse, SesApi, + SetIdentityHeadersInNotificationsEnabledResponse, TemplateData, TemplateName, VerificationAttributes, @@ -56,7 +61,7 @@ from localstack.http import Resource, Response from localstack.services.moto import call_moto from localstack.services.plugins import ServiceLifecycleHook -from localstack.services.ses.models import SentEmail, SentEmailBody +from localstack.services.ses.models import EmailType, SentEmail, SentEmailBody from localstack.utils.aws import arns from localstack.utils.files import mkdir from localstack.utils.strings import long_uid, to_str @@ -69,7 +74,7 @@ # Keep record of all sent emails # These can be retrieved via a service endpoint -EMAILS: Dict[MessageId, Dict[str, Any]] = {} +EMAILS: dict[MessageId, dict[str, Any]] = {} # Endpoint to access all the sent emails # (relative to LocalStack internal HTTP resources base endpoint) @@ -112,7 +117,7 @@ def _serialize(obj): LOGGER.debug("Email saved at: %s", path) -def recipients_from_destination(destination: Destination) -> List[str]: +def recipients_from_destination(destination: Destination) -> list[str]: """Get list of recipient email addresses from a Destination object.""" return ( destination.get("ToAddresses", []) @@ -184,7 +189,7 @@ def on_after_init(self): # Helpers # - def get_source_from_raw(self, raw_data: str) -> Optional[str]: + def get_source_from_raw(self, raw_data: str) -> str | None: """Given a raw representation of email, return the source/from field.""" entities = raw_data.split("\n") for entity in entities: @@ -210,7 +215,9 @@ def create_configuration_set_event_destination( emitter = SNSEmitter(context) emitter.emit_create_configuration_set_event_destination_test_message(sns_topic_arn) - # only register the event destiation if emitting the message worked + # FIXME: Moto stores the Event Destinations as a single value when it should be a list + # it only considers the last Event Destination created, when AWS is able to store multiple configurations + # only register the event destination if emitting the message worked try: result = call_moto(context) except CommonServiceException as e: @@ -269,6 +276,9 @@ def delete_configuration_set_event_destination( # FIXME: inconsistent state LOGGER.warning("inconsistent state encountered in ses backend") + # FIXME: Moto stores the Event Destinations as a single value when it should be a list + # it only considers the last Event Destination created, when AWS is able to store multiple configurations + # don't pop the whole value which should be a list but is currently a dict backend.config_set_event_destination.pop(configuration_set_name) return DeleteConfigurationSetEventDestinationResponse() @@ -358,25 +368,21 @@ def send_email( response = call_moto(context) backend = get_ses_backend(context) - emitter = SNSEmitter(context) - recipients = recipients_from_destination(destination) - for event_destination in backend.config_set_event_destination.values(): - if not event_destination["Enabled"]: - continue - - sns_destination_arn = event_destination.get("SNSDestination") - if not sns_destination_arn: - continue - - payload = SNSPayload( + if event_destinations := backend.config_set_event_destination.get(configuration_set_name): + recipients = recipients_from_destination(destination) + payload = EventDestinationPayload( message_id=response["MessageId"], sender_email=source, destination_addresses=recipients, tags=tags, ) - emitter.emit_send_event(payload, sns_destination_arn) - emitter.emit_delivery_event(payload, sns_destination_arn) + notify_event_destinations( + context=context, + event_destinations=event_destinations, + payload=payload, + email_type=EmailType.EMAIL, + ) text_part = message["Body"].get("Text", {}).get("Data") html_part = message["Body"].get("Html", {}).get("Data") @@ -414,25 +420,21 @@ def send_templated_email( response = call_moto(context) backend = get_ses_backend(context) - emitter = SNSEmitter(context) - recipients = recipients_from_destination(destination) - for event_destination in backend.config_set_event_destination.values(): - if not event_destination["Enabled"]: - continue - - sns_destination_arn = event_destination.get("SNSDestination") - if not sns_destination_arn: - continue - - payload = SNSPayload( + if event_destinations := backend.config_set_event_destination.get(configuration_set_name): + recipients = recipients_from_destination(destination) + payload = EventDestinationPayload( message_id=response["MessageId"], sender_email=source, destination_addresses=recipients, tags=tags, ) - emitter.emit_send_event(payload, sns_destination_arn, emit_source_arn=False) - emitter.emit_delivery_event(payload, sns_destination_arn) + notify_event_destinations( + context=context, + event_destinations=event_destinations, + payload=payload, + email_type=EmailType.TEMPLATED, + ) save_for_retrospection( SentEmail( @@ -477,23 +479,19 @@ def send_raw_email( backend = get_ses_backend(context) message = backend.send_raw_email(source, destinations, raw_data) - emitter = SNSEmitter(context) - for event_destination in backend.config_set_event_destination.values(): - if not event_destination["Enabled"]: - continue - - sns_destination_arn = event_destination.get("SNSDestination") - if not sns_destination_arn: - continue - - payload = SNSPayload( + if event_destinations := backend.config_set_event_destination.get(configuration_set_name): + payload = EventDestinationPayload( message_id=message.id, sender_email=source, destination_addresses=destinations, tags=tags, ) - emitter.emit_send_event(payload, sns_destination_arn) - emitter.emit_delivery_event(payload, sns_destination_arn) + notify_event_destinations( + context=context, + event_destinations=event_destinations, + payload=payload, + email_type=EmailType.RAW, + ) save_for_retrospection( SentEmail( @@ -524,13 +522,49 @@ def clone_receipt_rule_set( return CloneReceiptRuleSetResponse() + @handler("SetIdentityHeadersInNotificationsEnabled") + def set_identity_headers_in_notifications_enabled( + self, + context: RequestContext, + identity: Identity, + notification_type: NotificationType, + enabled: Enabled, + **kwargs, + ) -> SetIdentityHeadersInNotificationsEnabledResponse: + """ + Sets whether Amazon SES includes the original email headers in the Amazon SNS notifications + for a specified identity and notification type. + """ + # Validate notification_type + if notification_type not in ( + NotificationType.Bounce, + NotificationType.Complaint, + NotificationType.Delivery, + ): + raise InvalidParameterValue( + f"Invalid notification type: {notification_type}. " + "Valid values are: Bounce, Complaint, Delivery." + ) + + backend = get_ses_backend(context) + if identity not in backend.addresses: + raise MessageRejected(f"Identity {identity} is not verified or does not exist.") + + # Store the setting in the backend + if not hasattr(backend, "identity_headers_in_notifications_enabled"): + backend.identity_headers_in_notifications_enabled = {} + backend.identity_headers_in_notifications_enabled.setdefault(identity, {})[ + notification_type + ] = enabled + return SetIdentityHeadersInNotificationsEnabledResponse() + @dataclasses.dataclass(frozen=True) -class SNSPayload: +class EventDestinationPayload: message_id: str sender_email: Address destination_addresses: AddressList - tags: Optional[MessageTagList] + tags: MessageTagList | None class SNSEmitter: @@ -558,9 +592,9 @@ def emit_create_configuration_set_event_destination_test_message( ) def emit_send_event( - self, payload: SNSPayload, sns_topic_arn: str, emit_source_arn: bool = True + self, payload: EventDestinationPayload, sns_topic_arn: str, emit_source_arn: bool = True ): - now = datetime.now(tz=timezone.utc) + now = datetime.now(tz=UTC) tags = defaultdict(list) for every in payload.tags or []: @@ -594,8 +628,8 @@ def emit_send_event( except ClientError: LOGGER.exception("sending SNS message") - def emit_delivery_event(self, payload: SNSPayload, sns_topic_arn: str): - now = datetime.now(tz=timezone.utc) + def emit_delivery_event(self, payload: EventDestinationPayload, sns_topic_arn: str): + now = datetime.now(tz=UTC) tags = defaultdict(list) for every in payload.tags or []: @@ -640,6 +674,35 @@ def _client_for_topic(topic_arn: str) -> "SNSClient": ).sns +def notify_event_destinations( + context: RequestContext, + # FIXME: Moto stores the Event Destinations as a single value when it should be a list + event_destinations: dict, + payload: EventDestinationPayload, + email_type: EmailType, +): + emitter = SNSEmitter(context) + + if not isinstance(event_destinations, list): + event_destinations = [event_destinations] + + for event_destination in event_destinations: + if not event_destination["Enabled"]: + continue + + sns_destination_arn = event_destination.get("SNSDestination") + if not sns_destination_arn: + continue + + matching_event_types = event_destination.get("EventMatchingTypes") or [] + if EventType.send in matching_event_types: + emitter.emit_send_event( + payload, sns_destination_arn, emit_source_arn=email_type != EmailType.TEMPLATED + ) + if EventType.delivery in matching_event_types: + emitter.emit_delivery_event(payload, sns_destination_arn) + + class InvalidParameterValue(CommonServiceException): def __init__(self, message=None): super().__init__( diff --git a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py index 5baeb44cd6a82..ed1ece331f726 100644 --- a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py +++ b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,41 +14,41 @@ class SESEmailIdentityProperties(TypedDict): - EmailIdentity: Optional[str] - ConfigurationSetAttributes: Optional[ConfigurationSetAttributes] - DkimAttributes: Optional[DkimAttributes] - DkimDNSTokenName1: Optional[str] - DkimDNSTokenName2: Optional[str] - DkimDNSTokenName3: Optional[str] - DkimDNSTokenValue1: Optional[str] - DkimDNSTokenValue2: Optional[str] - DkimDNSTokenValue3: Optional[str] - DkimSigningAttributes: Optional[DkimSigningAttributes] - FeedbackAttributes: Optional[FeedbackAttributes] - MailFromAttributes: Optional[MailFromAttributes] + EmailIdentity: str | None + ConfigurationSetAttributes: ConfigurationSetAttributes | None + DkimAttributes: DkimAttributes | None + DkimDNSTokenName1: str | None + DkimDNSTokenName2: str | None + DkimDNSTokenName3: str | None + DkimDNSTokenValue1: str | None + DkimDNSTokenValue2: str | None + DkimDNSTokenValue3: str | None + DkimSigningAttributes: DkimSigningAttributes | None + FeedbackAttributes: FeedbackAttributes | None + MailFromAttributes: MailFromAttributes | None class ConfigurationSetAttributes(TypedDict): - ConfigurationSetName: Optional[str] + ConfigurationSetName: str | None class DkimSigningAttributes(TypedDict): - DomainSigningPrivateKey: Optional[str] - DomainSigningSelector: Optional[str] - NextSigningKeyLength: Optional[str] + DomainSigningPrivateKey: str | None + DomainSigningSelector: str | None + NextSigningKeyLength: str | None class DkimAttributes(TypedDict): - SigningEnabled: Optional[bool] + SigningEnabled: bool | None class MailFromAttributes(TypedDict): - BehaviorOnMxFailure: Optional[str] - MailFromDomain: Optional[str] + BehaviorOnMxFailure: str | None + MailFromDomain: str | None class FeedbackAttributes(TypedDict): - EmailForwardingEnabled: Optional[bool] + EmailForwardingEnabled: bool | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py index ca75f6be6c340..d80b72231e357 100644 --- a/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py +++ b/localstack-core/localstack/services/ses/resource_providers/aws_ses_emailidentity_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SESEmailIdentityProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SES::EmailIdentity" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ses.resource_providers.aws_ses_emailidentity import ( diff --git a/localstack-core/localstack/services/sns/models.py b/localstack-core/localstack/services/sns/models.py index a4e660e243207..bbbc923e74421 100644 --- a/localstack-core/localstack/services/sns/models.py +++ b/localstack-core/localstack/services/sns/models.py @@ -2,7 +2,7 @@ import time from dataclasses import dataclass, field from enum import StrEnum -from typing import Dict, List, Literal, Optional, TypedDict, Union +from typing import Literal, TypedDict from localstack.aws.api.sns import ( MessageAttributeMap, @@ -80,18 +80,18 @@ class SnsMessageType(StrEnum): @dataclass class SnsMessage: type: SnsMessageType - message: Union[ - str, Dict - ] # can be Dict if after being JSON decoded for validation if structure is `json` - message_attributes: Optional[MessageAttributeMap] = None - message_structure: Optional[str] = None - subject: Optional[str] = None - message_deduplication_id: Optional[str] = None - message_group_id: Optional[str] = None - token: Optional[str] = None + message: ( + str | dict + ) # can be Dict if after being JSON decoded for validation if structure is `json` + message_attributes: MessageAttributeMap | None = None + message_structure: str | None = None + subject: str | None = None + message_deduplication_id: str | None = None + message_group_id: str | None = None + token: str | None = None message_id: str = field(default_factory=long_uid) - is_fifo: Optional[bool] = False - sequencer_number: Optional[str] = None + is_fifo: bool | None = False + sequencer_number: str | None = None def __post_init__(self): if self.message_attributes is None: @@ -140,39 +140,39 @@ class SnsSubscription(TypedDict, total=False): Protocol: SnsProtocols SubscriptionArn: subscriptionARN PendingConfirmation: Literal["true", "false"] - Owner: Optional[str] - SubscriptionPrincipal: Optional[str] - FilterPolicy: Optional[str] + Owner: str | None + SubscriptionPrincipal: str | None + FilterPolicy: str | None FilterPolicyScope: Literal["MessageAttributes", "MessageBody"] RawMessageDelivery: Literal["true", "false"] ConfirmationWasAuthenticated: Literal["true", "false"] - SubscriptionRoleArn: Optional[str] - DeliveryPolicy: Optional[str] + SubscriptionRoleArn: str | None + DeliveryPolicy: str | None class SnsStore(BaseStore): # maps topic ARN to subscriptions ARN - topic_subscriptions: Dict[str, List[str]] = LocalAttribute(default=dict) + topic_subscriptions: dict[str, list[str]] = LocalAttribute(default=dict) # maps subscription ARN to SnsSubscription - subscriptions: Dict[str, SnsSubscription] = LocalAttribute(default=dict) + subscriptions: dict[str, SnsSubscription] = LocalAttribute(default=dict) # maps confirmation token to subscription ARN - subscription_tokens: Dict[str, str] = LocalAttribute(default=dict) + subscription_tokens: dict[str, str] = LocalAttribute(default=dict) # maps topic ARN to list of tags - sns_tags: Dict[str, List[Dict]] = LocalAttribute(default=dict) + sns_tags: dict[str, list[dict]] = LocalAttribute(default=dict) # cache of topic ARN to platform endpoint messages (used primarily for testing) - platform_endpoint_messages: Dict[str, List[Dict]] = LocalAttribute(default=dict) + platform_endpoint_messages: dict[str, list[dict]] = LocalAttribute(default=dict) # list of sent SMS messages - sms_messages: List[Dict] = LocalAttribute(default=list) + sms_messages: list[dict] = LocalAttribute(default=list) # filter policy are stored as JSON string in subscriptions, store the decoded result Dict - subscription_filter_policy: Dict[subscriptionARN, Dict] = LocalAttribute(default=dict) + subscription_filter_policy: dict[subscriptionARN, dict] = LocalAttribute(default=dict) - def get_topic_subscriptions(self, topic_arn: str) -> List[SnsSubscription]: + def get_topic_subscriptions(self, topic_arn: str) -> list[SnsSubscription]: topic_subscriptions = self.topic_subscriptions.get(topic_arn, []) subscriptions = [ subscription diff --git a/localstack-core/localstack/services/sns/provider.py b/localstack-core/localstack/services/sns/provider.py index e5d166ef3c72c..34bbf8b540e1f 100644 --- a/localstack-core/localstack/services/sns/provider.py +++ b/localstack-core/localstack/services/sns/provider.py @@ -3,7 +3,6 @@ import functools import json import logging -from typing import Dict, List from uuid import uuid4 from botocore.utils import InvalidArnException @@ -1130,7 +1129,7 @@ def register_sns_api_resource(router: Router): router.add(SNSServiceSubscriptionTokenApiResource()) -def _format_messages(sent_messages: List[Dict[str, str]], validated_keys: List[str]): +def _format_messages(sent_messages: list[dict[str, str]], validated_keys: list[str]): """ This method format the messages to be more readable and undo the format change that was needed for Moto Should be removed once we refactor SNS. diff --git a/localstack-core/localstack/services/sns/publisher.py b/localstack-core/localstack/services/sns/publisher.py index 9510885f51431..f69f977028255 100644 --- a/localstack-core/localstack/services/sns/publisher.py +++ b/localstack-core/localstack/services/sns/publisher.py @@ -9,7 +9,6 @@ import traceback from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field -from typing import Dict, List, Tuple, Union import requests from cryptography.hazmat.primitives import hashes @@ -61,9 +60,9 @@ class SnsPublishContext: @dataclass class SnsBatchPublishContext: - messages: List[SnsMessage] + messages: list[SnsMessage] store: SnsStore - request_headers: Dict[str, str] + request_headers: dict[str, str] topic_attributes: dict[str, str] = field(default_factory=dict) @@ -818,7 +817,7 @@ def _publish(self, context: SnsPublishContext, endpoint: str): # TODO: see about delivery log for individual endpoint message, need credentials for testing # store_delivery_log(subscriber, context, success=True) - def prepare_message(self, message_context: SnsMessage, endpoint: str) -> Union[str, Dict]: + def prepare_message(self, message_context: SnsMessage, endpoint: str) -> str | dict: platform_type = get_platform_type_from_endpoint_arn(endpoint) return { "TargetArn": endpoint, @@ -862,7 +861,7 @@ def get_application_platform_arn_from_endpoint_arn(endpoint_arn: str) -> str: return f"{base_arn}:app/{platform_type}/{app_name}" -def get_attributes_for_application_endpoint(endpoint_arn: str) -> Tuple[Dict, Dict]: +def get_attributes_for_application_endpoint(endpoint_arn: str) -> tuple[dict, dict]: """ Retrieve the attributes necessary to send a message directly to the platform (credentials and token) :param endpoint_arn: @@ -883,7 +882,7 @@ def get_attributes_for_application_endpoint(endpoint_arn: str) -> Tuple[Dict, Di def send_message_to_gcm( - context: SnsPublishContext, app_attributes: Dict[str, str], endpoint_attributes: Dict[str, str] + context: SnsPublishContext, app_attributes: dict[str, str], endpoint_attributes: dict[str, str] ) -> None: """ Send the message directly to GCM, with the credentials used when creating the PlatformApplication and the Endpoint @@ -1014,7 +1013,7 @@ def create_sns_message_body( def prepare_message_attributes( message_attributes: MessageAttributeMap, -) -> Dict[str, Dict[str, str]]: +) -> dict[str, dict[str, str]]: attributes = {} if not message_attributes: return attributes diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py index 650df889dff02..e98c24012fe5f 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack import config @@ -18,18 +18,18 @@ class SNSSubscriptionProperties(TypedDict): - Protocol: Optional[str] - TopicArn: Optional[str] - DeliveryPolicy: Optional[dict] - Endpoint: Optional[str] - FilterPolicy: Optional[dict] - FilterPolicyScope: Optional[str] - Id: Optional[str] - RawMessageDelivery: Optional[bool] - RedrivePolicy: Optional[dict] - Region: Optional[str] - ReplayPolicy: Optional[dict] - SubscriptionRoleArn: Optional[str] + Protocol: str | None + TopicArn: str | None + DeliveryPolicy: dict | None + Endpoint: str | None + FilterPolicy: dict | None + FilterPolicyScope: str | None + Id: str | None + RawMessageDelivery: bool | None + RedrivePolicy: dict | None + Region: str | None + ReplayPolicy: dict | None + SubscriptionRoleArn: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py index 01e23a1f30aed..f0be907619d54 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_subscription_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SNSSubscriptionProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SNS::Subscription" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sns.resource_providers.aws_sns_subscription import ( diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py index 7bc6720fd63f5..c3f8c5bae5279 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -16,27 +16,27 @@ class SNSTopicProperties(TypedDict): - ContentBasedDeduplication: Optional[bool] - DataProtectionPolicy: Optional[dict] - DisplayName: Optional[str] - FifoTopic: Optional[bool] - KmsMasterKeyId: Optional[str] - SignatureVersion: Optional[str] - Subscription: Optional[list[Subscription]] - Tags: Optional[list[Tag]] - TopicArn: Optional[str] - TopicName: Optional[str] - TracingConfig: Optional[str] + ContentBasedDeduplication: bool | None + DataProtectionPolicy: dict | None + DisplayName: str | None + FifoTopic: bool | None + KmsMasterKeyId: str | None + SignatureVersion: str | None + Subscription: list[Subscription] | None + Tags: list[Tag] | None + TopicArn: str | None + TopicName: str | None + TracingConfig: str | None class Subscription(TypedDict): - Endpoint: Optional[str] - Protocol: Optional[str] + Endpoint: str | None + Protocol: str | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py index de6a26a9482c5..b7d9b1d484d67 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topic_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SNSTopicProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SNS::Topic" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sns.resource_providers.aws_sns_topic import SNSTopicProvider diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py index 412a22a150a96..a9f88c2765538 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict from botocore.exceptions import ClientError @@ -18,9 +18,9 @@ class SNSTopicPolicyProperties(TypedDict): - PolicyDocument: Optional[dict | str] - Topics: Optional[list[str]] - Id: Optional[str] + PolicyDocument: dict | str | None + Topics: list[str] | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py index 9fbe0afe2d7e4..fc1faeff714f8 100644 --- a/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py +++ b/localstack-core/localstack/services/sns/resource_providers/aws_sns_topicpolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SNSTopicPolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SNS::TopicPolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sns.resource_providers.aws_sns_topicpolicy import ( diff --git a/localstack-core/localstack/services/sqs/models.py b/localstack-core/localstack/services/sqs/models.py index 02ef7bb0595e4..985dd743ed6fc 100644 --- a/localstack-core/localstack/services/sqs/models.py +++ b/localstack-core/localstack/services/sqs/models.py @@ -8,7 +8,6 @@ import time from datetime import datetime from queue import Empty -from typing import Dict, Optional, Set from localstack import config from localstack.aws.api import RequestContext @@ -53,11 +52,11 @@ class SqsMessage: created: float visibility_timeout: int receive_count: int - delay_seconds: Optional[int] - receipt_handles: Set[str] - last_received: Optional[float] - first_received: Optional[float] - visibility_deadline: Optional[float] + delay_seconds: int | None + receipt_handles: set[str] + last_received: float | None + first_received: float | None + visibility_deadline: float | None deleted: bool priority: float message_deduplication_id: str @@ -104,15 +103,15 @@ def __init__( ) @property - def message_group_id(self) -> Optional[str]: + def message_group_id(self) -> str | None: return self.message["Attributes"].get(MessageSystemAttributeName.MessageGroupId) @property - def message_deduplication_id(self) -> Optional[str]: + def message_deduplication_id(self) -> str | None: return self.message["Attributes"].get(MessageSystemAttributeName.MessageDeduplicationId) @property - def dead_letter_queue_source_arn(self) -> Optional[str]: + def dead_letter_queue_source_arn(self) -> str | None: return self.message["Attributes"].get(MessageSystemAttributeName.DeadLetterQueueSourceArn) @property @@ -272,11 +271,11 @@ class SqsQueue: tags: TagMap purge_in_progress: bool - purge_timestamp: Optional[float] + purge_timestamp: float | None - delayed: Set[SqsMessage] - inflight: Set[SqsMessage] - receipts: Dict[str, SqsMessage] + delayed: set[SqsMessage] + inflight: set[SqsMessage] + receipts: dict[str, SqsMessage] def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None: self.name = name @@ -393,13 +392,13 @@ def url(self, context: RequestContext) -> str: ) @property - def redrive_policy(self) -> Optional[dict]: + def redrive_policy(self) -> dict | None: if policy_document := self.attributes.get(QueueAttributeName.RedrivePolicy): return json.loads(policy_document) return None @property - def max_receive_count(self) -> Optional[int]: + def max_receive_count(self) -> int | None: """ Returns the maxReceiveCount attribute of the redrive policy. If no redrive policy is set, then it returns None. @@ -678,7 +677,7 @@ def get_queue_attributes(self, attribute_names: AttributeNameList = None) -> dic if QueueAttributeName.All in attribute_names: attribute_names = self.attributes.keys() - result: Dict[QueueAttributeName, str] = {} + result: dict[QueueAttributeName, str] = {} for attr in attribute_names: try: @@ -740,7 +739,7 @@ def _pre_delete_checks(self, standard_message: SqsMessage, receipt_handle: str) class StandardQueue(SqsQueue): visible: InterruptiblePriorityQueue[SqsMessage] - inflight: Set[SqsMessage] + inflight: set[SqsMessage] def __init__(self, name: str, region: str, account_id: str, attributes=None, tags=None) -> None: super().__init__(name, region, account_id, attributes, tags) @@ -955,7 +954,7 @@ class FifoQueue(SqsQueue): TODO: raise exceptions when trying to remove a message with an expired receipt handle """ - deduplication: Dict[str, SqsMessage] + deduplication: dict[str, SqsMessage] message_groups: dict[str, MessageGroup] inflight_groups: set[MessageGroup] message_group_queue: InterruptibleQueue @@ -1062,10 +1061,8 @@ def put( # use the attribute from the queue fifo_message.visibility_timeout = self.visibility_timeout - if delay_seconds is not None: - fifo_message.delay_seconds = delay_seconds - else: - fifo_message.delay_seconds = self.delay_seconds + # FIFO queues always use the queue level setting for 'DelaySeconds' + fifo_message.delay_seconds = self.delay_seconds original_message = self.deduplication.get(dedup_id) if ( @@ -1154,7 +1151,7 @@ def receive( timeout = wait_time_seconds or 0 start = time.time() - received_groups: Set[MessageGroup] = set() + received_groups: set[MessageGroup] = set() # collect messages over potentially multiple groups while True: @@ -1310,11 +1307,11 @@ def clear(self): class SqsStore(BaseStore): - queues: Dict[str, SqsQueue] = LocalAttribute(default=dict) + queues: dict[str, SqsQueue] = LocalAttribute(default=dict) - deleted: Dict[str, float] = LocalAttribute(default=dict) + deleted: dict[str, float] = LocalAttribute(default=dict) - move_tasks: Dict[str, MessageMoveTask] = LocalAttribute(default=dict) + move_tasks: dict[str, MessageMoveTask] = LocalAttribute(default=dict) """Maps task IDs to their ``MoveMessageTask`` object. Task IDs can be found by decoding a task handle.""" def expire_deleted(self): diff --git a/localstack-core/localstack/services/sqs/provider.py b/localstack-core/localstack/services/sqs/provider.py index d031b7d952490..521aba382e8ed 100644 --- a/localstack-core/localstack/services/sqs/provider.py +++ b/localstack-core/localstack/services/sqs/provider.py @@ -5,9 +5,10 @@ import re import threading import time +from collections.abc import Iterable from concurrent.futures.thread import ThreadPoolExecutor from itertools import islice -from typing import Dict, Iterable, List, Literal, Optional, Tuple +from typing import Literal from botocore.utils import InvalidArnException from moto.sqs.models import BINARY_TYPE_FIELD_INDEX, STRING_TYPE_FIELD_INDEX @@ -310,7 +311,7 @@ class CloudwatchPublishWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None def publish_approximate_cloudwatch_metrics(self): """Publishes the metrics for ApproximateNumberOfMessagesVisible, ApproximateNumberOfMessagesNotVisible @@ -391,7 +392,7 @@ class QueueUpdateWorker: def __init__(self) -> None: super().__init__() self.scheduler = Scheduler() - self.thread: Optional[FuncThread] = None + self.thread: FuncThread | None = None self.mutex = threading.RLock() def iter_queues(self) -> Iterable[SqsQueue]: @@ -460,7 +461,7 @@ class MessageMoveTaskManager: def __init__(self, stores: AccountRegionBundle[SqsStore] = None) -> None: self.stores = stores or sqs_stores self.mutex = threading.RLock() - self.move_tasks: dict[str, MessageMoveTask] = dict() + self.move_tasks: dict[str, MessageMoveTask] = {} self.executor = ThreadPoolExecutor(max_workers=100, thread_name_prefix="sqs-move-message") def submit(self, move_task: MessageMoveTask): @@ -744,7 +745,7 @@ def _get_and_serialize_messages( def _collect_messages( self, queue: SqsQueue, show_invisible: bool = False, show_delayed: bool = False - ) -> List[Message]: + ) -> list[Message]: """ Retrieves from a given SqsQueue all visible messages without causing any side effects (not setting any receive timestamps, receive counts, or visibility state). @@ -756,7 +757,7 @@ def _collect_messages( """ receipt_handle = "SQS/BACKDOOR/ACCESS" # dummy receipt handle - sqs_messages: List[SqsMessage] = [] + sqs_messages: list[SqsMessage] = [] if show_invisible: sqs_messages.extend(queue.inflight) @@ -811,7 +812,7 @@ class SqsProvider(SqsApi, ServiceLifecycleHook): - UntagQueue """ - queues: Dict[str, SqsQueue] + queues: dict[str, SqsQueue] def __init__(self) -> None: super().__init__() @@ -877,8 +878,8 @@ def _require_queue_by_arn(self, context: RequestContext, queue_arn: str) -> SqsQ def _resolve_queue( self, context: RequestContext, - queue_name: Optional[str] = None, - queue_url: Optional[str] = None, + queue_name: str | None = None, + queue_url: str | None = None, ) -> SqsQueue: """ Determines the name of the queue from available information (request context, queue URL) to return the respective queue, @@ -1670,8 +1671,8 @@ def _create_message_attributes( self, context: RequestContext, message_system_attributes: MessageBodySystemAttributeMap = None, - ) -> Dict[MessageSystemAttributeName, str]: - result: Dict[MessageSystemAttributeName, str] = { + ) -> dict[MessageSystemAttributeName, str]: + result: dict[MessageSystemAttributeName, str] = { MessageSystemAttributeName.SenderId: context.account_id, # not the account ID in AWS MessageSystemAttributeName.SentTimestamp: str(now(millis=True)), } @@ -1705,7 +1706,7 @@ def _validate_actions(self, actions: ActionNameList): def _assert_batch( self, - batch: List, + batch: list, *, require_fifo_queue_params: bool = False, require_message_deduplication_id: bool = False, @@ -1741,7 +1742,7 @@ def _assert_batch( else: visited.add(entry_id) - def _assert_valid_batch_size(self, batch: List, max_message_size: int): + def _assert_valid_batch_size(self, batch: list, max_message_size: int): batch_message_size = sum( _message_body_size(entry.get("MessageBody")) + _message_attributes_size(entry.get("MessageAttributes")) @@ -1752,7 +1753,7 @@ def _assert_valid_batch_size(self, batch: List, max_message_size: int): error += f" You have sent {batch_message_size} bytes." raise BatchRequestTooLong(error) - def _assert_valid_message_ids(self, batch: List): + def _assert_valid_message_ids(self, batch: list): batch_id_regex = r"^[\w-]{1,80}$" for message in batch: if not re.match(batch_id_regex, message.get("Id", "")): @@ -1782,7 +1783,7 @@ def _stop_cloudwatch_metrics_reporting(self): # Method from moto's attribute_md5 of moto/sqs/models.py, separated from the Message Object -def _create_message_attribute_hash(message_attributes) -> Optional[str]: +def _create_message_attribute_hash(message_attributes) -> str | None: # To avoid the need to check for dict conformity everytime we invoke this function if not isinstance(message_attributes, dict): return @@ -1810,8 +1811,8 @@ def _create_message_attribute_hash(message_attributes) -> Optional[str]: def resolve_queue_location( - context: RequestContext, queue_name: Optional[str] = None, queue_url: Optional[str] = None -) -> Tuple[str, Optional[str], str]: + context: RequestContext, queue_name: str | None = None, queue_url: str | None = None +) -> tuple[str, str | None, str]: """ Resolves a queue location from the given information. @@ -1868,7 +1869,7 @@ def to_sqs_api_message( return message -def message_filter_attributes(message: Message, names: Optional[AttributeNameList]): +def message_filter_attributes(message: Message, names: AttributeNameList | None): """ Utility function filter from the given message (in-place) the system attributes from the given list. It will apply all rules according to: @@ -1892,7 +1893,7 @@ def message_filter_attributes(message: Message, names: Optional[AttributeNameLis del message["Attributes"][k] -def message_filter_message_attributes(message: Message, names: Optional[MessageAttributeNameList]): +def message_filter_message_attributes(message: Message, names: MessageAttributeNameList | None): """ Utility function filter from the given message (in-place) the message attributes from the given list. It will apply all rules according to: diff --git a/localstack-core/localstack/services/sqs/query_api.py b/localstack-core/localstack/services/sqs/query_api.py index 6d5a33ee4bd5d..ac0a836377ceb 100644 --- a/localstack-core/localstack/services/sqs/query_api.py +++ b/localstack-core/localstack/services/sqs/query_api.py @@ -4,7 +4,6 @@ to make the request.""" import logging -from typing import Dict, Optional, Tuple from urllib.parse import urlencode from botocore.exceptions import ClientError @@ -179,7 +178,7 @@ def handle_request(request: Request, region: str) -> Response: ) -def try_call_sqs(request: Request, region: str) -> Tuple[Dict, OperationModel]: +def try_call_sqs(request: Request, region: str) -> tuple[dict, OperationModel]: action = request.values.get("Action") if not action: raise UnknownOperationException() @@ -209,7 +208,7 @@ def try_call_sqs(request: Request, region: str) -> Tuple[Dict, OperationModel]: # Extract from auth header to allow cross-account operations # TODO: permissions encoded in URL as AUTHPARAMS cannot be accounted for in this method, which is not a big # problem yet since we generally don't enforce permissions. - account_id: Optional[str] = extract_access_key_id_from_auth_header(headers) + account_id: str | None = extract_access_key_id_from_auth_header(headers) client = connect_to( region_name=region, diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py index 52b39da351d96..861449ed53bc5 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,29 +15,29 @@ class SQSQueueProperties(TypedDict): - Arn: Optional[str] - ContentBasedDeduplication: Optional[bool] - DeduplicationScope: Optional[str] - DelaySeconds: Optional[int] - FifoQueue: Optional[bool] - FifoThroughputLimit: Optional[str] - KmsDataKeyReusePeriodSeconds: Optional[int] - KmsMasterKeyId: Optional[str] - MaximumMessageSize: Optional[int] - MessageRetentionPeriod: Optional[int] - QueueName: Optional[str] - QueueUrl: Optional[str] - ReceiveMessageWaitTimeSeconds: Optional[int] - RedriveAllowPolicy: Optional[dict | str] - RedrivePolicy: Optional[dict | str] - SqsManagedSseEnabled: Optional[bool] - Tags: Optional[list[Tag]] - VisibilityTimeout: Optional[int] + Arn: str | None + ContentBasedDeduplication: bool | None + DeduplicationScope: str | None + DelaySeconds: int | None + FifoQueue: bool | None + FifoThroughputLimit: str | None + KmsDataKeyReusePeriodSeconds: int | None + KmsMasterKeyId: str | None + MaximumMessageSize: int | None + MessageRetentionPeriod: int | None + QueueName: str | None + QueueUrl: str | None + ReceiveMessageWaitTimeSeconds: int | None + RedriveAllowPolicy: dict | str | None + RedrivePolicy: dict | str | None + SqsManagedSseEnabled: bool | None + Tags: list[Tag] | None + VisibilityTimeout: int | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py index 45c892bc5dade..e71ec95f4f0b5 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queue_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SQSQueueProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SQS::Queue" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sqs.resource_providers.aws_sqs_queue import SQSQueueProvider diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py index 40dd99191b659..4c29b3e000fb2 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy.py @@ -3,7 +3,7 @@ import json from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -15,9 +15,9 @@ class SQSQueuePolicyProperties(TypedDict): - PolicyDocument: Optional[dict] - Queues: Optional[list[str]] - Id: Optional[str] + PolicyDocument: dict | None + Queues: list[str] | None + Id: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py index fc6ce346cf5e3..8f656bea99512 100644 --- a/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py +++ b/localstack-core/localstack/services/sqs/resource_providers/aws_sqs_queuepolicy_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SQSQueuePolicyProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SQS::QueuePolicy" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.sqs.resource_providers.aws_sqs_queuepolicy import ( diff --git a/localstack-core/localstack/services/sqs/utils.py b/localstack-core/localstack/services/sqs/utils.py index a280128ad7b66..a67fd4c65c472 100644 --- a/localstack-core/localstack/services/sqs/utils.py +++ b/localstack-core/localstack/services/sqs/utils.py @@ -3,7 +3,7 @@ import json import re import time -from typing import Literal, NamedTuple, Optional, Tuple +from typing import Literal, NamedTuple from urllib.parse import urlparse from localstack.aws.api.sqs import QueueAttributeName, ReceiptHandleIsInvalid @@ -36,7 +36,7 @@ def is_sqs_queue_url(url: str) -> bool: def guess_endpoint_strategy_and_host( host: str, -) -> Tuple[Literal["standard", "domain", "path"], str]: +) -> tuple[Literal["standard", "domain", "path"], str]: """ This method is used for the dynamic endpoint strategy. It heuristically determines a tuple where the first element is the endpoint strategy, and the second is the part of the host after the endpoint prefix and region. @@ -77,7 +77,7 @@ def is_fifo_queue(queue): return "true" == queue.attributes.get(QueueAttributeName.FifoQueue, "false").lower() -def parse_queue_url(queue_url: str) -> Tuple[str, Optional[str], str]: +def parse_queue_url(queue_url: str) -> tuple[str, str | None, str]: """ Parses an SQS Queue URL and returns a triple of account_id, region and queue_name. diff --git a/localstack-core/localstack/services/ssm/provider.py b/localstack-core/localstack/services/ssm/provider.py index 7787daa091383..10a22197ee6ce 100644 --- a/localstack-core/localstack/services/ssm/provider.py +++ b/localstack-core/localstack/services/ssm/provider.py @@ -3,7 +3,6 @@ import logging import time from abc import ABC -from typing import Dict, Optional from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api.ssm import ( @@ -339,7 +338,7 @@ def describe_maintenance_window_tasks( # utility methods below @staticmethod - def _denormalize_param_name_in_response(param_result: Dict, param_name: str): + def _denormalize_param_name_in_response(param_result: dict, param_name: str): result_name = param_result["Name"] if result_name != param_name and result_name.lstrip("/") == param_name.lstrip("/"): param_result["Name"] = param_name @@ -373,7 +372,7 @@ def _normalize_name(param_name: ParameterName, validate=False) -> ParameterName: @staticmethod def _get_secrets_information( account_id: str, region_name: str, name: ParameterName, resource_name: str - ) -> Optional[GetParameterResult]: + ) -> GetParameterResult | None: client = connect_to(aws_access_key_id=account_id, region_name=region_name).secretsmanager try: secret_info = client.get_secret_value(SecretId=resource_name) diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py index 974a6b0676242..8200b62c7885e 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,23 +14,23 @@ class SSMMaintenanceWindowProperties(TypedDict): - AllowUnassociatedTargets: Optional[bool] - Cutoff: Optional[int] - Duration: Optional[int] - Name: Optional[str] - Schedule: Optional[str] - Description: Optional[str] - EndDate: Optional[str] - Id: Optional[str] - ScheduleOffset: Optional[int] - ScheduleTimezone: Optional[str] - StartDate: Optional[str] - Tags: Optional[list[Tag]] + AllowUnassociatedTargets: bool | None + Cutoff: int | None + Duration: int | None + Name: str | None + Schedule: str | None + Description: str | None + EndDate: str | None + Id: str | None + ScheduleOffset: int | None + ScheduleTimezone: str | None + StartDate: str | None + Tags: list[Tag] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py index c7f5ef1c2e50a..8d4fe0b2aa855 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindow_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMMaintenanceWindowProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SSM::MaintenanceWindow" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_maintenancewindow import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py index a6f8ef6029dbf..4cef768c14fc8 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,18 +14,18 @@ class SSMMaintenanceWindowTargetProperties(TypedDict): - ResourceType: Optional[str] - Targets: Optional[list[Targets]] - WindowId: Optional[str] - Description: Optional[str] - Id: Optional[str] - Name: Optional[str] - OwnerInformation: Optional[str] + ResourceType: str | None + Targets: list[Targets] | None + WindowId: str | None + Description: str | None + Id: str | None + Name: str | None + OwnerInformation: str | None class Targets(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py index c16b5208eff20..b3f9bcd876553 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtarget_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMMaintenanceWindowTargetProviderPlugin(CloudFormationResourceProviderPlu name = "AWS::SSM::MaintenanceWindowTarget" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_maintenancewindowtarget import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py index 01b2f165a9aaa..5f8a33d8c7f78 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,80 +14,80 @@ class SSMMaintenanceWindowTaskProperties(TypedDict): - Priority: Optional[int] - TaskArn: Optional[str] - TaskType: Optional[str] - WindowId: Optional[str] - CutoffBehavior: Optional[str] - Description: Optional[str] - Id: Optional[str] - LoggingInfo: Optional[LoggingInfo] - MaxConcurrency: Optional[str] - MaxErrors: Optional[str] - Name: Optional[str] - ServiceRoleArn: Optional[str] - Targets: Optional[list[Target]] - TaskInvocationParameters: Optional[TaskInvocationParameters] - TaskParameters: Optional[dict] + Priority: int | None + TaskArn: str | None + TaskType: str | None + WindowId: str | None + CutoffBehavior: str | None + Description: str | None + Id: str | None + LoggingInfo: LoggingInfo | None + MaxConcurrency: str | None + MaxErrors: str | None + Name: str | None + ServiceRoleArn: str | None + Targets: list[Target] | None + TaskInvocationParameters: TaskInvocationParameters | None + TaskParameters: dict | None class Target(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class MaintenanceWindowStepFunctionsParameters(TypedDict): - Input: Optional[str] - Name: Optional[str] + Input: str | None + Name: str | None class CloudWatchOutputConfig(TypedDict): - CloudWatchLogGroupName: Optional[str] - CloudWatchOutputEnabled: Optional[bool] + CloudWatchLogGroupName: str | None + CloudWatchOutputEnabled: bool | None class NotificationConfig(TypedDict): - NotificationArn: Optional[str] - NotificationEvents: Optional[list[str]] - NotificationType: Optional[str] + NotificationArn: str | None + NotificationEvents: list[str] | None + NotificationType: str | None class MaintenanceWindowRunCommandParameters(TypedDict): - CloudWatchOutputConfig: Optional[CloudWatchOutputConfig] - Comment: Optional[str] - DocumentHash: Optional[str] - DocumentHashType: Optional[str] - DocumentVersion: Optional[str] - NotificationConfig: Optional[NotificationConfig] - OutputS3BucketName: Optional[str] - OutputS3KeyPrefix: Optional[str] - Parameters: Optional[dict] - ServiceRoleArn: Optional[str] - TimeoutSeconds: Optional[int] + CloudWatchOutputConfig: CloudWatchOutputConfig | None + Comment: str | None + DocumentHash: str | None + DocumentHashType: str | None + DocumentVersion: str | None + NotificationConfig: NotificationConfig | None + OutputS3BucketName: str | None + OutputS3KeyPrefix: str | None + Parameters: dict | None + ServiceRoleArn: str | None + TimeoutSeconds: int | None class MaintenanceWindowLambdaParameters(TypedDict): - ClientContext: Optional[str] - Payload: Optional[str] - Qualifier: Optional[str] + ClientContext: str | None + Payload: str | None + Qualifier: str | None class MaintenanceWindowAutomationParameters(TypedDict): - DocumentVersion: Optional[str] - Parameters: Optional[dict] + DocumentVersion: str | None + Parameters: dict | None class TaskInvocationParameters(TypedDict): - MaintenanceWindowAutomationParameters: Optional[MaintenanceWindowAutomationParameters] - MaintenanceWindowLambdaParameters: Optional[MaintenanceWindowLambdaParameters] - MaintenanceWindowRunCommandParameters: Optional[MaintenanceWindowRunCommandParameters] - MaintenanceWindowStepFunctionsParameters: Optional[MaintenanceWindowStepFunctionsParameters] + MaintenanceWindowAutomationParameters: MaintenanceWindowAutomationParameters | None + MaintenanceWindowLambdaParameters: MaintenanceWindowLambdaParameters | None + MaintenanceWindowRunCommandParameters: MaintenanceWindowRunCommandParameters | None + MaintenanceWindowStepFunctionsParameters: MaintenanceWindowStepFunctionsParameters | None class LoggingInfo(TypedDict): - Region: Optional[str] - S3Bucket: Optional[str] - S3Prefix: Optional[str] + Region: str | None + S3Bucket: str | None + S3Prefix: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py index 494b10f07bd48..906565014d2c9 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_maintenancewindowtask_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMMaintenanceWindowTaskProviderPlugin(CloudFormationResourceProviderPlugi name = "AWS::SSM::MaintenanceWindowTask" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_maintenancewindowtask import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py index 95ea2ecb4d214..e6ee2b967eac6 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,15 +14,15 @@ class SSMParameterProperties(TypedDict): - Type: Optional[str] - Value: Optional[str] - AllowedPattern: Optional[str] - DataType: Optional[str] - Description: Optional[str] - Name: Optional[str] - Policies: Optional[str] - Tags: Optional[dict] - Tier: Optional[str] + Type: str | None + Value: str | None + AllowedPattern: str | None + DataType: str | None + Description: str | None + Name: str | None + Policies: str | None + Tags: dict | None + Tier: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py index e75f657f22100..5cda5c985867a 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_parameter_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMParameterProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SSM::Parameter" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_parameter import ( diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py index 7c3623c981eee..927cbdcbc962d 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,52 +14,52 @@ class SSMPatchBaselineProperties(TypedDict): - Name: Optional[str] - ApprovalRules: Optional[RuleGroup] - ApprovedPatches: Optional[list[str]] - ApprovedPatchesComplianceLevel: Optional[str] - ApprovedPatchesEnableNonSecurity: Optional[bool] - Description: Optional[str] - GlobalFilters: Optional[PatchFilterGroup] - Id: Optional[str] - OperatingSystem: Optional[str] - PatchGroups: Optional[list[str]] - RejectedPatches: Optional[list[str]] - RejectedPatchesAction: Optional[str] - Sources: Optional[list[PatchSource]] - Tags: Optional[list[Tag]] + Name: str | None + ApprovalRules: RuleGroup | None + ApprovedPatches: list[str] | None + ApprovedPatchesComplianceLevel: str | None + ApprovedPatchesEnableNonSecurity: bool | None + Description: str | None + GlobalFilters: PatchFilterGroup | None + Id: str | None + OperatingSystem: str | None + PatchGroups: list[str] | None + RejectedPatches: list[str] | None + RejectedPatchesAction: str | None + Sources: list[PatchSource] | None + Tags: list[Tag] | None class PatchFilter(TypedDict): - Key: Optional[str] - Values: Optional[list[str]] + Key: str | None + Values: list[str] | None class PatchFilterGroup(TypedDict): - PatchFilters: Optional[list[PatchFilter]] + PatchFilters: list[PatchFilter] | None class Rule(TypedDict): - ApproveAfterDays: Optional[int] - ApproveUntilDate: Optional[dict] - ComplianceLevel: Optional[str] - EnableNonSecurity: Optional[bool] - PatchFilterGroup: Optional[PatchFilterGroup] + ApproveAfterDays: int | None + ApproveUntilDate: dict | None + ComplianceLevel: str | None + EnableNonSecurity: bool | None + PatchFilterGroup: PatchFilterGroup | None class RuleGroup(TypedDict): - PatchRules: Optional[list[Rule]] + PatchRules: list[Rule] | None class PatchSource(TypedDict): - Configuration: Optional[str] - Name: Optional[str] - Products: Optional[list[str]] + Configuration: str | None + Name: str | None + Products: list[str] | None class Tag(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py index 3991ae2eec102..3282273f9fbaf 100644 --- a/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py +++ b/localstack-core/localstack/services/ssm/resource_providers/aws_ssm_patchbaseline_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class SSMPatchBaselineProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::SSM::PatchBaseline" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.ssm.resource_providers.aws_ssm_patchbaseline import ( diff --git a/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py b/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py index 61c7d073abb19..2340d3f49aa00 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py +++ b/localstack-core/localstack/services/stepfunctions/asl/antlt4utils/antlr4utils.py @@ -1,11 +1,10 @@ import ast -from typing import Optional from antlr4 import ParserRuleContext from antlr4.tree.Tree import ParseTree, TerminalNodeImpl -def is_production(pt: ParseTree, rule_index: Optional[int] = None) -> Optional[ParserRuleContext]: +def is_production(pt: ParseTree, rule_index: int | None = None) -> ParserRuleContext | None: if isinstance(pt, ParserRuleContext): prc = pt.getRuleContext() # noqa if rule_index is not None: @@ -14,7 +13,7 @@ def is_production(pt: ParseTree, rule_index: Optional[int] = None) -> Optional[P return None -def is_terminal(pt: ParseTree, token_type: Optional[int] = None) -> Optional[TerminalNodeImpl]: +def is_terminal(pt: ParseTree, token_type: int | None = None) -> TerminalNodeImpl | None: if isinstance(pt, TerminalNodeImpl): if token_type is not None: return pt if pt.getSymbol().type == token_type else None @@ -22,7 +21,7 @@ def is_terminal(pt: ParseTree, token_type: Optional[int] = None) -> Optional[Ter return None -def from_string_literal(parser_rule_context: ParserRuleContext) -> Optional[str]: +def from_string_literal(parser_rule_context: ParserRuleContext) -> str | None: string_literal = parser_rule_context.getText() if string_literal.startswith('"') and string_literal.endswith('"'): string_literal = string_literal[1:-1] diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py index 494fb10db595d..326a3d468d35f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl.py @@ -15,7 +15,7 @@ def __init__(self, declaration_bindings: list[AssignDeclBinding]): self.declaration_bindings = declaration_bindings def _eval_body(self, env: Environment) -> None: - declarations: dict[str, Any] = dict() + declarations: dict[str, Any] = {} for declaration_binding in self.declaration_bindings: declaration_binding.eval(env=env) binding: dict[str, Any] = env.stack.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py index 8695bfea82678..cbe48103785ba 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_decl_binding.py @@ -15,5 +15,5 @@ def __init__(self, binding: AssignTemplateBinding): self.binding = binding def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) self.binding.eval(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py index b2ff0a71ec733..097fa3403fb92 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_array.py @@ -13,7 +13,7 @@ def __init__(self, values: list[AssignTemplateValue]): self.values = values def _eval_body(self, env: Environment) -> None: - arr = list() + arr = [] for value in self.values: value.eval(env) arr.append(env.stack.pop()) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py index 2b4c451595e9b..c4644cdaeb146 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/assign/assign_template_value_object.py @@ -16,6 +16,6 @@ def __init__(self, bindings: list[AssignTemplateBinding]): self.bindings = bindings def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) for binding in self.bindings: binding.eval(env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py index 44705370da1cd..872f14c17c707 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/catch/catcher_decl.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.assign.assign_decl import AssignDecl from localstack.services.stepfunctions.asl.component.common.catch.catcher_outcome import ( @@ -34,19 +34,19 @@ class CatcherDecl(EvalComponent): error_equals: Final[ErrorEqualsDecl] next_decl: Final[Next] - result_path: Final[Optional[ResultPath]] - assign: Final[Optional[AssignDecl]] - output: Final[Optional[Output]] - comment: Final[Optional[Comment]] + result_path: Final[ResultPath | None] + assign: Final[AssignDecl | None] + output: Final[Output | None] + comment: Final[Comment | None] def __init__( self, error_equals: ErrorEqualsDecl, next_decl: Next, - result_path: Optional[ResultPath], - assign: Optional[AssignDecl], - output: Optional[Output], - comment: Optional[Comment], + result_path: ResultPath | None, + assign: AssignDecl | None, + output: Output | None, + comment: Comment | None, ): self.error_equals = error_equals self.next_decl = next_decl diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py index 6d4ed3954ad1f..14155be70b7e9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/custom_error_name.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.error_name.error_name import ErrorName @@ -10,7 +10,7 @@ class CustomErrorName(ErrorName): States MAY report errors with other names, which MUST NOT begin with the prefix "States.". """ - def __init__(self, error_name: Optional[str]): + def __init__(self, error_name: str | None): if error_name is not None and error_name.startswith(ILLEGAL_CUSTOM_ERROR_PREFIX): raise ValueError( f"Custom Error Names MUST NOT begin with the prefix 'States.', got '{error_name}'." diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py index 50e09e290aa4f..443c1daff7f54 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/error_name.py @@ -1,18 +1,18 @@ from __future__ import annotations import abc -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.component import Component class ErrorName(Component, abc.ABC): - error_name: Final[Optional[str]] + error_name: Final[str | None] - def __init__(self, error_name: Optional[str]): + def __init__(self, error_name: str | None): self.error_name = error_name - def matches(self, error_name: Optional[str]) -> bool: + def matches(self, error_name: str | None) -> bool: return self.error_name == error_name def __eq__(self, other): diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py index 4624ea025395b..2320cd5ad9f23 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/failure_event.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( EvaluationFailedEventDetails, @@ -16,16 +16,16 @@ class FailureEvent: state_name: Final[str] source_event_id: Final[int] - error_name: Final[Optional[ErrorName]] + error_name: Final[ErrorName | None] event_type: Final[HistoryEventType] - event_details: Final[Optional[EventDetails]] + event_details: Final[EventDetails | None] def __init__( self, env: Environment, - error_name: Optional[ErrorName], + error_name: ErrorName | None, event_type: HistoryEventType, - event_details: Optional[EventDetails] = None, + event_details: EventDetails | None = None, ): self.state_name = env.next_state_name self.source_event_id = env.event_history_context.source_event_id @@ -40,7 +40,7 @@ class FailureEventException(Exception): def __init__(self, failure_event: FailureEvent): self.failure_event = failure_event - def extract_error_cause_pair(self) -> Optional[tuple[Optional[str], Optional[str]]]: + def extract_error_cause_pair(self) -> tuple[str | None, str | None] | None: if self.failure_event.event_details is None: return None @@ -54,7 +54,7 @@ def extract_error_cause_pair(self) -> Optional[tuple[Optional[str], Optional[str cause = failure_event_spec["cause"] return error, cause - def get_evaluation_failed_event_details(self) -> Optional[EvaluationFailedEventDetails]: + def get_evaluation_failed_event_details(self) -> EvaluationFailedEventDetails | None: original_failed_event_details = self.failure_event.event_details[ "evaluationFailedEventDetails" ] @@ -80,7 +80,7 @@ def get_evaluation_failed_event_details(self) -> Optional[EvaluationFailedEventD return evaluation_failed_event_details - def get_execution_failed_event_details(self) -> Optional[ExecutionFailedEventDetails]: + def get_execution_failed_event_details(self) -> ExecutionFailedEventDetails | None: maybe_error_cause_pair = self.extract_error_cause_pair() if maybe_error_cause_pair is None: return None diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py index 9dcda9350ffcd..8cc6cff7c67d8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/error_name/states_error_name_type.py @@ -40,7 +40,7 @@ def _error_name(error_name: StatesErrorNameType) -> str: def _reverse_error_name_lookup() -> dict[str, StatesErrorNameType]: - lookup: dict[str, StatesErrorNameType] = dict() + lookup: dict[str, StatesErrorNameType] = {} for error_name in StatesErrorNameType: error_text: str = _error_name(error_name) lookup[error_text] = error_name diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py index 3833f14c0abdc..dbdc0f581ec74 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_binding.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.jsonata.jsonata_template_value import ( JSONataTemplateValue, @@ -17,7 +17,7 @@ def __init__(self, identifier: str, value: JSONataTemplateValue): self.identifier = identifier self.value = value - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return self.identifier def _eval_body(self, env: Environment) -> None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py index 552b168299e2a..38e01dda43dde 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_array.py @@ -13,7 +13,7 @@ def __init__(self, values: list[JSONataTemplateValue]): self.values = values def _eval_body(self, env: Environment) -> None: - arr = list() + arr = [] for value in self.values: value.eval(env) arr.append(env.stack.pop()) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py index 81b1c19a00c53..e3e1a09b5847c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/jsonata/jsonata_template_value_object.py @@ -16,6 +16,6 @@ def __init__(self, bindings: list[JSONataTemplateBinding]): self.bindings = bindings def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) for binding in self.bindings: binding.eval(env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py index 8c0d4e6cbb4e7..4adc2c01254d2 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/input_path.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( @@ -22,14 +22,14 @@ class InputPath(EvalComponent): - string_sampler: Final[Optional[StringSampler]] + string_sampler: Final[StringSampler | None] - def __init__(self, string_sampler: Optional[StringSampler]): + def __init__(self, string_sampler: StringSampler | None): self.string_sampler = string_sampler def _eval_body(self, env: Environment) -> None: if self.string_sampler is None: - env.stack.append(dict()) + env.stack.append({}) return if isinstance(self.string_sampler, StringJsonPath): # JsonPaths are sampled from a given state, hence pass the state's input. diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py index b40586aa8e716..f9d853e1f26d5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/output_path.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( @@ -21,14 +21,14 @@ class OutputPath(EvalComponent): - string_sampler: Final[Optional[StringSampler]] + string_sampler: Final[StringSampler | None] - def __init__(self, string_sampler: Optional[StringSampler]): + def __init__(self, string_sampler: StringSampler | None): self.string_sampler = string_sampler def _eval_body(self, env: Environment) -> None: if self.string_sampler is None: - env.states.reset(input_value=dict()) + env.states.reset(input_value={}) return try: self.string_sampler.eval(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py index bfcb3f2cfe91d..67224ed145652 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/path/result_path.py @@ -1,5 +1,5 @@ import copy -from typing import Final, Optional +from typing import Final from jsonpath_ng import parse @@ -10,9 +10,9 @@ class ResultPath(EvalComponent): DEFAULT_PATH: Final[str] = "$" - result_path_src: Final[Optional[str]] + result_path_src: Final[str | None] - def __init__(self, result_path_src: Optional[str]): + def __init__(self, result_path_src: str | None): self.result_path_src = result_path_src def _eval_body(self, env: Environment) -> None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py index 3c9f7a3bdf9b0..86d78d4697e2b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadarr/payload_arr.py @@ -11,7 +11,7 @@ def __init__(self, payload_values: list[PayloadValue]): self.payload_values: Final[list[PayloadValue]] = payload_values def _eval_body(self, env: Environment) -> None: - arr = list() + arr = [] for payload_value in self.payload_values: payload_value.eval(env) arr.append(env.stack.pop()) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py index 1b7d7fb527634..daef4e7bc9605 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadbinding/payload_binding.py @@ -1,5 +1,5 @@ import abc -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.common.payload.payloadvalue.payload_value import ( PayloadValue, @@ -16,7 +16,7 @@ class PayloadBinding(PayloadValue, abc.ABC): def __init__(self, field: str): self.field = field - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return self.field @abc.abstractmethod @@ -36,7 +36,7 @@ def __init__(self, field: str, string_expression_simple: StringExpressionSimple) super().__init__(field=field) self.string_expression_simple = string_expression_simple - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return f"{self.field}.$" def _eval_val(self, env: Environment) -> Any: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py index dce0cc10b2cc1..0279a85ea5cb4 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/payload/payloadvalue/payloadtmpl/payload_tmpl.py @@ -14,6 +14,6 @@ def __init__(self, payload_bindings: list[PayloadBinding]): self.payload_bindings: Final[list[PayloadBinding]] = payload_bindings def _eval_body(self, env: Environment) -> None: - env.stack.append(dict()) + env.stack.append({}) for payload_binding in self.payload_bindings: payload_binding.eval(env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py index 108a4f97790e5..f4871cbd3a67a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/retry/retrier_decl.py @@ -1,7 +1,7 @@ from __future__ import annotations import time -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.error_name.error_equals_decl import ( @@ -38,17 +38,17 @@ class RetrierDecl(EvalComponent): backoff_rate: Final[BackoffRateDecl] max_delay_seconds: Final[MaxDelaySecondsDecl] jitter_strategy: Final[JitterStrategyDecl] - comment: Final[Optional[Comment]] + comment: Final[Comment | None] def __init__( self, error_equals: ErrorEqualsDecl, - interval_seconds: Optional[IntervalSecondsDecl] = None, - max_attempts: Optional[MaxAttemptsDecl] = None, - backoff_rate: Optional[BackoffRateDecl] = None, - max_delay_seconds: Optional[MaxDelaySecondsDecl] = None, - jitter_strategy: Optional[JitterStrategyDecl] = None, - comment: Optional[Comment] = None, + interval_seconds: IntervalSecondsDecl | None = None, + max_attempts: MaxAttemptsDecl | None = None, + backoff_rate: BackoffRateDecl | None = None, + max_delay_seconds: MaxDelaySecondsDecl | None = None, + jitter_strategy: JitterStrategyDecl | None = None, + comment: Comment | None = None, ): self.error_equals = error_equals self.interval_seconds = interval_seconds or IntervalSecondsDecl() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py index 3f4be28c7e14c..c784e9e48f45a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/string/string_expression.py @@ -1,6 +1,6 @@ import abc import copy -from typing import Any, Final, Optional +from typing import Any, Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.events.utils import to_json_str @@ -46,7 +46,7 @@ class StringExpression(EvalComponent, abc.ABC): def __init__(self, literal_value: str): self.literal_value = literal_value - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return None @@ -129,7 +129,7 @@ def _eval_body(self, env: Environment) -> None: expression_variable_references: set[VariableReference] = ( extract_jsonata_variable_references(self.expression) ) - variable_declarations_list = list() + variable_declarations_list = [] if self.query_language_mode == QueryLanguageMode.JSONata: # Sample $states values into expression. states_variable_declarations: VariableDeclarations = ( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py b/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py index 03ae1a6ba2e33..fb90f963e4ad8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/common/timeouts/timeout.py @@ -1,5 +1,5 @@ import abc -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( ExecutionFailedEventDetails, @@ -44,13 +44,13 @@ def _eval_body(self, env: Environment) -> None: class TimeoutSeconds(Timeout): DEFAULT_TIMEOUT_SECONDS: Final[int] = 99999999 - def __init__(self, timeout_seconds: int, is_default: Optional[bool] = None): + def __init__(self, timeout_seconds: int, is_default: bool | None = None): if not isinstance(timeout_seconds, int) and timeout_seconds <= 0: raise ValueError( f"Expected non-negative integer for TimeoutSeconds, got '{timeout_seconds}' instead." ) self.timeout_seconds: Final[int] = timeout_seconds - self.is_default: Optional[bool] = is_default + self.is_default: bool | None = is_default def is_default_value(self) -> bool: if self.is_default is not None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py index cd7940208f5cc..3db4e599e3273 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/eval_component.py @@ -1,6 +1,5 @@ import abc import logging -from typing import Optional from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( FailureEventException, @@ -14,7 +13,7 @@ class EvalComponent(Component, abc.ABC): - __heap_key: Optional[str] = None + __heap_key: str | None = None @property def heap_key(self) -> str: @@ -82,5 +81,5 @@ def eval(self, env: Environment) -> None: def _eval_body(self, env: Environment) -> None: raise NotImplementedError() - def _field_name(self) -> Optional[str]: + def _field_name(self) -> str | None: return self.__class__.__name__ diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py index 6438471c8becb..351331f8b41db 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/argument/argument.py @@ -1,5 +1,5 @@ import abc -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.common.string.string_expression import ( StringVariableSample, @@ -32,9 +32,9 @@ def _eval_body(self, env: Environment) -> None: class ArgumentLiteral(Argument): - definition_value: Final[Optional[Any]] + definition_value: Final[Any | None] - def __init__(self, definition_value: Optional[Any]): + def __init__(self, definition_value: Any | None): self.definition_value = definition_value def _eval_argument(self, env: Environment) -> Any: @@ -97,7 +97,7 @@ def __init__(self, arguments: list[Argument]): self.size = len(arguments) def _eval_argument(self, env: Environment) -> Any: - values = list() + values = [] for argument in self.arguments: argument.eval(env=env) argument_value = env.stack.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py index a12b2780c0faf..b718988e0fcd9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/array/array_partition.py @@ -60,7 +60,7 @@ def _eval_body(self, env: Environment) -> None: @staticmethod def _to_chunks(array: list, chunk_size: int): - chunks = list() + chunks = [] for i in range(0, len(array), chunk_size): chunks.append(array[i : i + chunk_size]) return chunks diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py index 86e8b50050518..e446a44dc84ee 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/generic/string_format.py @@ -95,7 +95,7 @@ def _to_str_repr(value: Any) -> str: value_parts: list[str] = list(map(StringFormat._to_str_repr, value)) return f"[{', '.join(value_parts)}]" elif isinstance(value, dict): - dict_items = list() + dict_items = [] for d_key, d_value in value.items(): d_value_lit = StringFormat._to_str_repr(d_value) dict_items.append(f"{d_key}={d_value_lit}") diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py index 5cce091f0fd85..50d98145dece8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_array.py @@ -24,7 +24,7 @@ def __init__(self, argument_list: ArgumentList): def _eval_body(self, env: Environment) -> None: self.argument_list.eval(env=env) - values: list[Any] = list() + values: list[Any] = [] for _ in range(self.argument_list.size): values.append(env.stack.pop()) values.reverse() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py index 8b71a07fbd122..9ea03ed3c7437 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/function/statesfunction/states_function_format.py @@ -41,7 +41,7 @@ def _eval_body(self, env: Environment) -> None: # TODO: investigate behaviour for incorrect number of arguments in string format. self.argument_list.eval(env=env) - values: list[Any] = list() + values: list[Any] = [] for _ in range(self.argument_list.size): values.append(env.stack.pop()) string_format: str = values.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py index 8602aed713e63..435dd446a98c8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/intrinsic/jsonata.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.jsonata.jsonata import ( VariableDeclarations, @@ -75,9 +75,9 @@ def get_intrinsic_functions_declarations( variable_references: set[VariableReference], ) -> VariableDeclarations: - declarations: list[str] = list() + declarations: list[str] = [] for variable_reference in variable_references: - declaration: Optional[VariableDeclarations] = _DECLARATION_BY_VARIABLE_REFERENCE.get( + declaration: VariableDeclarations | None = _DECLARATION_BY_VARIABLE_REFERENCE.get( variable_reference ) if declaration: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py b/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py index e86a5cd076620..330f2a3e599b9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/program/program.py @@ -1,6 +1,6 @@ import logging import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( ExecutionAbortedEventDetails, @@ -47,18 +47,18 @@ class Program(EvalComponent): query_language: Final[QueryLanguage] start_at: Final[StartAt] states: Final[States] - timeout_seconds: Final[Optional[TimeoutSeconds]] - comment: Final[Optional[Comment]] - version: Final[Optional[Version]] + timeout_seconds: Final[TimeoutSeconds | None] + comment: Final[Comment | None] + version: Final[Version | None] def __init__( self, query_language: QueryLanguage, start_at: StartAt, states: States, - timeout_seconds: Optional[TimeoutSeconds], - comment: Optional[Comment] = None, - version: Optional[Version] = None, + timeout_seconds: TimeoutSeconds | None, + comment: Comment | None = None, + version: Version | None = None, ): self.query_language = query_language self.start_at = start_at @@ -68,7 +68,7 @@ def __init__( self.version = version def _get_state(self, state_name: str) -> CommonStateField: - state: Optional[CommonStateField] = self.states.states.get(state_name, None) + state: CommonStateField | None = self.states.states.get(state_name, None) if state is None: raise ValueError(f"No such state {state}.") return state @@ -111,7 +111,7 @@ def _eval_body(self, env: Environment) -> None: program_state: ProgramState = env.program_state() if isinstance(program_state, ProgramError): exec_failed_event_details = select_from_typed_dict( - typed_dict=ExecutionFailedEventDetails, obj=program_state.error or dict() + typed_dict=ExecutionFailedEventDetails, obj=program_state.error or {} ) env.event_manager.add_event( context=env.event_history_context, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py b/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py index 61fa0a8c3f757..fcda4338e4763 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/program/states.py @@ -4,4 +4,4 @@ class States(Component): def __init__(self): - self.states: dict[str, CommonStateField] = dict() + self.states: dict[str, CommonStateField] = {} diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py index 7e7004b27e31d..fe28c07e41e44 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state.py @@ -2,10 +2,9 @@ import abc import datetime -import json import logging from abc import ABC -from typing import Final, Optional, Union +from typing import Any, Final from localstack.aws.api.stepfunctions import ( ExecutionFailedEventDetails, @@ -74,27 +73,27 @@ class CommonStateField(EvalComponent, ABC): continue_with: ContinueWith # Holds a human-readable description of the state. - comment: Optional[Comment] + comment: Comment | None # A path that selects a portion of the state's input to be passed to the state's state_task for processing. # If omitted, it has the value $ which designates the entire input. - input_path: Optional[InputPath] + input_path: InputPath | None # A path that selects a portion of the state's output to be passed to the next state. # If omitted, it has the value $ which designates the entire output. - output_path: Optional[OutputPath] + output_path: OutputPath | None - assign_decl: Optional[AssignDecl] + assign_decl: AssignDecl | None - output: Optional[Output] + output: Output | None state_entered_event_type: Final[HistoryEventType] - state_exited_event_type: Final[Optional[HistoryEventType]] + state_exited_event_type: Final[HistoryEventType | None] def __init__( self, state_entered_event_type: HistoryEventType, - state_exited_event_type: Optional[HistoryEventType], + state_exited_event_type: HistoryEventType | None, ): self.state_entered_event_type = state_entered_event_type self.state_exited_event_type = state_exited_event_type @@ -163,7 +162,7 @@ def _get_state_exited_event_details(self, env: Environment) -> StateExitedEventD event_details["assignedVariablesDetails"] = {"truncated": False} # noqa return event_details - def _verify_size_quota(self, env: Environment, value: Union[str, json]) -> None: + def _verify_size_quota(self, env: Environment, value: str | Any) -> None: is_within: bool = is_within_size_quota(value) if is_within: return @@ -215,7 +214,7 @@ def _eval_body(self, env: Environment) -> None: ), ) env.states.context_object.context_object_data["State"] = StateData( - EnteredTime=datetime.datetime.now(tz=datetime.timezone.utc).isoformat(), Name=self.name + EnteredTime=datetime.datetime.now(tz=datetime.UTC).isoformat(), Name=self.name ) self._eval_state_input(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py index a946eec561292..bf12b6c628174 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/choice_rule.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.assign.assign_decl import AssignDecl from localstack.services.stepfunctions.asl.component.common.comment import Comment @@ -12,19 +12,19 @@ class ChoiceRule(EvalComponent): - comparison: Final[Optional[Comparison]] - next_stmt: Final[Optional[Next]] - comment: Final[Optional[Comment]] - assign: Final[Optional[AssignDecl]] - output: Final[Optional[Output]] + comparison: Final[Comparison | None] + next_stmt: Final[Next | None] + comment: Final[Comment | None] + assign: Final[AssignDecl | None] + output: Final[Output | None] def __init__( self, - comparison: Optional[Comparison], - next_stmt: Optional[Next], - comment: Optional[Comment], - assign: Optional[AssignDecl], - output: Optional[Output], + comparison: Comparison | None, + next_stmt: Next | None, + comment: Comment | None, + assign: AssignDecl | None, + output: Output | None, ): self.comparison = comparison self.next_stmt = next_stmt diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py index e998ae1a50a0c..33885eda4ea8e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/comparison/operator/implementations/is_operator.py @@ -1,6 +1,6 @@ import datetime import logging -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.state.state_choice.comparison.comparison_operator_type import ( ComparisonOperatorType, @@ -88,7 +88,7 @@ def impl_name() -> str: return str(ComparisonOperatorType.IsTimestamp) @staticmethod - def string_to_timestamp(string: str) -> Optional[datetime.datetime]: + def string_to_timestamp(string: str) -> datetime.datetime | None: try: return datetime.datetime.strptime(string, IsTimestamp.TIMESTAMP_FORMAT) except Exception: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py index 99d21029a3fc3..3e6bcc9e92daa 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.aws.api.stepfunctions import HistoryEventType from localstack.services.stepfunctions.asl.component.common.flow.end import End from localstack.services.stepfunctions.asl.component.common.flow.next import Next @@ -16,7 +14,7 @@ class StateChoice(CommonStateField): choices_decl: ChoicesDecl - default_state: Optional[DefaultDecl] + default_state: DefaultDecl | None def __init__(self): super(StateChoice, self).__init__( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py index c32150cb3eb12..ac1a441fd2c33 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/execute_state.py @@ -3,7 +3,7 @@ import logging import threading from threading import Thread -from typing import Any, Optional +from typing import Any from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.catch.catch_decl import CatchDecl @@ -44,7 +44,7 @@ class ExecutionState(CommonStateField, abc.ABC): def __init__( self, state_entered_event_type: HistoryEventType, - state_exited_event_type: Optional[HistoryEventType], + state_exited_event_type: HistoryEventType | None, ): super().__init__( state_entered_event_type=state_entered_event_type, @@ -54,20 +54,20 @@ def __init__( # Specifies where (in the input) to place the results of executing the state_task that's specified in Resource. # The input is then filtered as specified by the OutputPath field (if present) before being used as the # state's output. - self.result_path: Optional[ResultPath] = None + self.result_path: ResultPath | None = None # ResultSelector (Optional) # Pass a collection of key value pairs, where the values are static or selected from the result. - self.result_selector: Optional[ResultSelector] = None + self.result_selector: ResultSelector | None = None # Retry (Optional) # An array of objects, called Retriers, that define a retry policy if the state encounters runtime errors. - self.retry: Optional[RetryDecl] = None + self.retry: RetryDecl | None = None # Catch (Optional) # An array of objects, called Catchers, that define a fallback state. This state is executed if the state # encounters runtime errors and its retry policy is exhausted or isn't defined. - self.catch: Optional[CatchDecl] = None + self.catch: CatchDecl | None = None # TimeoutSeconds (Optional) # If the state_task runs longer than the specified seconds, this state fails with a States.Timeout error name. @@ -93,7 +93,7 @@ def __init__( # HeartbeatSecondsPath. When resolved, the reference path must select fields whose values are positive integers. # A Task state cannot include both HeartbeatSeconds and HeartbeatSecondsPath # HeartbeatSeconds and HeartbeatSecondsPath fields are encoded by the Heartbeat type. - self.heartbeat: Optional[Heartbeat] = None + self.heartbeat: Heartbeat | None = None def from_state_props(self, state_props: StateProps) -> None: super().from_state_props(state_props=state_props) @@ -170,8 +170,8 @@ def _evaluate_with_timeout(self, env: Environment) -> None: frame: Environment = env.open_frame() frame.states.reset(input_value=env.states.get_input()) frame.stack = copy.deepcopy(env.stack) - execution_outputs: list[Any] = list() - execution_exceptions: list[Optional[Exception]] = [None] + execution_outputs: list[Any] = [] + execution_exceptions: list[Exception | None] = [None] terminated_event = threading.Event() def _exec_and_notify(): diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py index ed8e325034c56..a52420acdb6de 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/item_reader_decl.py @@ -1,5 +1,5 @@ import copy -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.parargs import Parargs from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent @@ -26,15 +26,15 @@ class ItemReader(EvalComponent): resource_eval: Final[ResourceEval] - parargs: Final[Optional[Parargs]] - reader_config: Final[Optional[ReaderConfig]] - resource_output_transformer: Optional[ResourceOutputTransformer] + parargs: Final[Parargs | None] + reader_config: Final[ReaderConfig | None] + resource_output_transformer: ResourceOutputTransformer | None def __init__( self, resource: Resource, - parargs: Optional[Parargs], - reader_config: Optional[ReaderConfig], + parargs: Parargs | None, + reader_config: ReaderConfig | None, ): self.resource_eval = resource_eval_for(resource=resource) self.parargs = parargs @@ -65,7 +65,7 @@ def _eval_body(self, env: Environment) -> None: if self.parargs: self.parargs.eval(env=env) else: - env.stack.append(dict()) + env.stack.append({}) self.resource_eval.eval_resource(env=env) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py index fff888b474b5a..58fbbc390bd96 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/reader_config/reader_config_decl.py @@ -1,4 +1,4 @@ -from typing import Final, Optional, TypedDict +from typing import Final, TypedDict from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_reader.reader_config.csv_header_location import ( @@ -34,7 +34,7 @@ class CSVHeaderLocationOutput(str): class ReaderConfigOutput(TypedDict): InputType: InputTypeOutput CSVHeaderLocation: CSVHeaderLocationOutput - CSVHeaders: Optional[CSVHeadersOutput] + CSVHeaders: CSVHeadersOutput | None MaxItemsValue: MaxItemsValueOutput @@ -42,14 +42,14 @@ class ReaderConfig(EvalComponent): input_type: Final[InputType] max_items_decl: Final[MaxItemsDecl] csv_header_location: Final[CSVHeaderLocation] - csv_headers: Optional[CSVHeaders] + csv_headers: CSVHeaders | None def __init__( self, input_type: InputType, csv_header_location: CSVHeaderLocation, - csv_headers: Optional[CSVHeaders], - max_items_decl: Optional[MaxItemsDecl], + csv_headers: CSVHeaders | None, + max_items_decl: MaxItemsDecl | None, ): self.input_type = input_type self.max_items_decl = max_items_decl or MaxItemsInt() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py index 262c4f00ca540..d37ca8bbaeb7c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_eval_s3.py @@ -1,6 +1,7 @@ from __future__ import annotations -from typing import Callable, Final +from collections.abc import Callable +from typing import Final from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_reader.resource_eval.resource_eval import ( ResourceEval, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py index 065aacdbc56fc..a67b7489bd362 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/item_reader/resource_eval/resource_output_transformer/resource_output_transformer_csv.py @@ -59,9 +59,9 @@ def _eval_body(self, env: Environment) -> None: ) raise FailureEventException(failure_event=failure_event) - transformed_outputs = list() + transformed_outputs = [] for row in csv_reader_slice: - transformed_output = dict() + transformed_output = {} for i, header in enumerate(headers): transformed_output[header] = row[i] if i < len(row) else "" transformed_outputs.append( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py index 841a9db4f453a..78db0aa23a15a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/distributed_iteration_component.py @@ -2,7 +2,7 @@ import abc import json -from typing import Any, Final, Optional +from typing import Any, Final from localstack.aws.api.stepfunctions import ( HistoryEventType, @@ -50,8 +50,8 @@ class DistributedIterationComponentEvalInput(InlineIterationComponentEvalInput): - item_reader: Final[Optional[ItemReader]] - label: Final[Optional[str]] + item_reader: Final[ItemReader | None] + label: Final[str | None] map_run_record: Final[MapRunRecord] def __init__( @@ -59,12 +59,12 @@ def __init__( state_name: str, max_concurrency: int, input_items: list[json], - parameters: Optional[Parameters], - item_selector: Optional[ItemSelector], - item_reader: Optional[ItemReader], + parameters: Parameters | None, + item_selector: ItemSelector | None, + item_reader: ItemReader | None, tolerated_failure_count: int, tolerated_failure_percentage: float, - label: Optional[str], + label: str | None, map_run_record: MapRunRecord, ): super().__init__( @@ -118,7 +118,7 @@ def _map_run( job_pool.await_jobs() - worker_exception: Optional[Exception] = job_pool.get_worker_exception() + worker_exception: Exception | None = job_pool.get_worker_exception() if worker_exception is not None: raise worker_exception diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py index 3eb020678142c..88cb25730ae4e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/inline_iteration_component.py @@ -3,7 +3,7 @@ import abc import json import threading -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.flow.start_at import StartAt @@ -38,16 +38,16 @@ class InlineIterationComponentEvalInput: state_name: Final[str] max_concurrency: Final[int] input_items: Final[list[json]] - parameters: Final[Optional[Parameters]] - item_selector: Final[Optional[ItemSelector]] + parameters: Final[Parameters | None] + item_selector: Final[ItemSelector | None] def __init__( self, state_name: str, max_concurrency: int, input_items: list[json], - parameters: Optional[Parameters], - item_selector: Optional[ItemSelector], + parameters: Parameters | None, + item_selector: ItemSelector | None, ): self.state_name = state_name self.max_concurrency = max_concurrency @@ -65,7 +65,7 @@ def __init__( start_at: StartAt, states: States, processor_config: ProcessorConfig, - comment: Optional[Comment], + comment: Comment | None, ): super().__init__( query_language=query_language, start_at=start_at, states=states, comment=comment @@ -105,7 +105,7 @@ def _eval_body(self, env: Environment) -> None: job_pool.await_jobs() - worker_exception: Optional[Exception] = job_pool.get_worker_exception() + worker_exception: Exception | None = job_pool.get_worker_exception() if worker_exception is not None: raise worker_exception diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py index bde4c49bdf073..1034d4c344fa5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/distributed_item_processor_worker.py @@ -1,5 +1,5 @@ import logging -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( FailureEventException, @@ -44,8 +44,8 @@ def __init__( job_pool: JobPool, env: Environment, item_reader: ItemReader, - parameters: Optional[Parameters], - item_selector: Optional[ItemSelector], + parameters: Parameters | None, + item_selector: ItemSelector | None, map_run_record: MapRunRecord, ): super().__init__( @@ -124,7 +124,7 @@ def _eval_job(self, env: Environment, job: Job) -> None: self._map_run_record.item_counter.running.offset(-1) job.job_output = job_output - def _eval_pool(self, job: Optional[Job], worker_frame: Environment) -> None: + def _eval_pool(self, job: Job | None, worker_frame: Environment) -> None: if job is None: self._env.delete_frame(worker_frame) return diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py index 2562108ebac80..f8f16fcc225b5 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/inline_item_processor_worker.py @@ -1,5 +1,5 @@ import logging -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.parargs import Parameters from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_selector import ( @@ -17,16 +17,16 @@ class InlineItemProcessorWorker(IterationWorker): - _parameters: Final[Optional[Parameters]] - _item_selector: Final[Optional[ItemSelector]] + _parameters: Final[Parameters | None] + _item_selector: Final[ItemSelector | None] def __init__( self, work_name: str, job_pool: JobPool, env: Environment, - item_selector: Optional[ItemSelector], - parameters: Optional[Parameters], + item_selector: ItemSelector | None, + parameters: Parameters | None, ): super().__init__(work_name=work_name, job_pool=job_pool, env=env) self._item_selector = item_selector diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py index 52599e7abf489..7769816fed5b0 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/itemprocessor/map_run_record.py @@ -2,7 +2,7 @@ import datetime import threading from collections import OrderedDict -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( Arn, @@ -98,7 +98,7 @@ class MapRunRecord: item_counter: Final[ItemCounter] start_date: Timestamp status: MapRunStatus - stop_date: Optional[Timestamp] + stop_date: Timestamp | None # TODO: add support for failure toleration fields. tolerated_failure_count: int tolerated_failure_percentage: float @@ -110,7 +110,7 @@ def __init__( max_concurrency: int, tolerated_failure_count: int, tolerated_failure_percentage: float, - label: Optional[str], + label: str | None, ): self.update_event = threading.Event() ( @@ -123,7 +123,7 @@ def __init__( self.max_concurrency = max_concurrency self.execution_counter = ExecutionCounter() self.item_counter = ItemCounter() - self.start_date = datetime.datetime.now(tz=datetime.timezone.utc) + self.start_date = datetime.datetime.now(tz=datetime.UTC) self.status = MapRunStatus.RUNNING self.stop_date = None self.tolerated_failure_count = tolerated_failure_count @@ -131,7 +131,7 @@ def __init__( @staticmethod def _generate_map_run_arns( - state_machine_arn: Arn, label: Optional[str] + state_machine_arn: Arn, label: str | None ) -> tuple[LongArn, LongArn]: # Generate a new MapRunArn given the StateMachineArn, such that: # inp: arn:aws:states::111111111111:stateMachine: @@ -144,7 +144,7 @@ def _generate_map_run_arns( def set_stop(self, status: MapRunStatus): self.status = status - self.stop_date = datetime.datetime.now(tz=datetime.timezone.utc) + self.stop_date = datetime.datetime.now(tz=datetime.UTC) def describe(self) -> DescribeMapRunOutput: describe_output = DescribeMapRunOutput( @@ -176,9 +176,9 @@ def list_item(self) -> MapRunListItem: def update( self, - max_concurrency: Optional[int], - tolerated_failure_count: Optional[int], - tolerated_failure_percentage: Optional[float], + max_concurrency: int | None, + tolerated_failure_count: int | None, + tolerated_failure_percentage: float | None, ) -> None: if max_concurrency is not None: self.max_concurrency = max_concurrency @@ -198,7 +198,7 @@ def __init__(self): def add(self, map_run_record: MapRunRecord) -> None: self._pool[map_run_record.map_run_arn] = map_run_record - def get(self, map_run_arn: LongArn) -> Optional[MapRunRecord]: + def get(self, map_run_arn: LongArn) -> MapRunRecord | None: return self._pool.get(map_run_arn) def get_all(self) -> list[MapRunRecord]: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py index 92e1be15ccd64..edfb073231985 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_component.py @@ -1,7 +1,7 @@ from __future__ import annotations import abc -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.flow.start_at import StartAt @@ -18,14 +18,14 @@ class IterationComponent(EvalComponent, abc.ABC): _query_language: Final[QueryLanguage] _start_at: Final[StartAt] _states: Final[States] - _comment: Final[Optional[Comment]] + _comment: Final[Comment | None] def __init__( self, query_language: QueryLanguage, start_at: StartAt, states: States, - comment: Optional[Comment], + comment: Comment | None, ): self._query_language = query_language self._start_at = start_at diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py index b26b87ec1437e..6a5250c31202a 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_declaration.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.comment import Comment from localstack.services.stepfunctions.asl.component.common.flow.start_at import StartAt @@ -11,7 +11,7 @@ class IterationDecl(Component): - comment: Final[Optional[Comment]] + comment: Final[Comment | None] query_language: Final[QueryLanguage] start_at: Final[StartAt] states: Final[States] @@ -19,7 +19,7 @@ class IterationDecl(Component): def __init__( self, - comment: Optional[Comment], + comment: Comment | None, query_language: QueryLanguage, start_at: StartAt, states: States, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py index 1603149ca0b57..e137a02e55567 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iteration_worker.py @@ -1,6 +1,6 @@ import abc import logging -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, MapIterationEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( @@ -169,7 +169,7 @@ def _eval_job(self, env: Environment, job: Job) -> None: finally: job.job_output = job_output - def _eval_pool(self, job: Optional[Job], worker_frame: Environment) -> None: + def _eval_pool(self, job: Job | None, worker_frame: Environment) -> None: # Note: the frame has to be closed before the job, to ensure the owner environment is correctly updated # before the evaluation continues; map states await for job termination not workers termination. if job is None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py index 583ab6e666473..9b6fce1a861f7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/distributed_iterator_worker.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( FailureEventException, ) @@ -35,9 +33,9 @@ def __init__( work_name: str, job_pool: JobPool, env: Environment, - parameters: Optional[Parameters], + parameters: Parameters | None, map_run_record: MapRunRecord, - item_selector: Optional[ItemSelector], + item_selector: ItemSelector | None, ): super().__init__( work_name=work_name, @@ -99,7 +97,7 @@ def _eval_job(self, env: Environment, job: Job) -> None: self._map_run_record.item_counter.running.offset(-1) job.job_output = job_output - def _eval_pool(self, job: Optional[Job], worker_frame: Environment) -> None: + def _eval_pool(self, job: Job | None, worker_frame: Environment) -> None: # Note: the frame has to be closed before the job, to ensure the owner environment is correctly updated # before the evaluation continues; map states await for job termination not workers termination. if job is None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py index 6100e412df44c..2f73f57c2b6fb 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator.py @@ -1,7 +1,6 @@ from __future__ import annotations import logging -from typing import Optional from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.iteration.inline_iteration_component import ( InlineIterationComponent, @@ -26,7 +25,7 @@ class InlineIteratorEvalInput(InlineIterationComponentEvalInput): class InlineIterator(InlineIterationComponent): - _eval_input: Optional[InlineIteratorEvalInput] + _eval_input: InlineIteratorEvalInput | None def _create_worker( self, env: Environment, eval_input: InlineIteratorEvalInput, job_pool: JobPool diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py index 45db68a00e8b1..f21c68962e4bc 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/iterator/inline_iterator_worker.py @@ -1,5 +1,5 @@ import logging -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.common.parargs import Parameters from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.item_selector import ( @@ -17,16 +17,16 @@ class InlineIteratorWorker(IterationWorker): - _parameters: Final[Optional[Parameters]] - _item_selector: Final[Optional[ItemSelector]] + _parameters: Final[Parameters | None] + _item_selector: Final[ItemSelector | None] def __init__( self, work_name: str, job_pool: JobPool, env: Environment, - item_selector: Optional[ItemSelector], - parameters: Optional[Parameters], + item_selector: ItemSelector | None, + parameters: Parameters | None, ): super().__init__(work_name=work_name, job_pool=job_pool, env=env) self._item_selector = item_selector diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py index 1ef24a6e17593..7cf76f1ef17b9 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/iteration/job.py @@ -1,7 +1,7 @@ import copy import logging import threading -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.component.program.program import Program from localstack.services.stepfunctions.asl.utils.encoding import to_json_str @@ -12,10 +12,10 @@ class Job: job_index: Final[int] job_program: Final[Program] - job_input: Final[Optional[Any]] - job_output: Optional[Any] + job_input: Final[Any | None] + job_output: Any | None - def __init__(self, job_index: int, job_program: Program, job_input: Optional[Any]): + def __init__(self, job_index: int, job_program: Program, job_input: Any | None): self.job_index = job_index self.job_program = job_program self.job_input = job_input @@ -24,9 +24,9 @@ def __init__(self, job_index: int, job_program: Program, job_input: Optional[Any class JobClosed: job_index: Final[int] - job_output: Optional[Any] + job_output: Any | None - def __init__(self, job_index: int, job_output: Optional[Any]): + def __init__(self, job_index: int, job_output: Any | None): self.job_index = job_index self.job_output = job_output @@ -37,7 +37,7 @@ def __hash__(self): class JobPool: _mutex: Final[threading.Lock] _termination_event: Final[threading.Event] - _worker_exception: Optional[Exception] + _worker_exception: Exception | None _jobs_number: Final[int] _open_jobs: Final[list[Job]] @@ -56,7 +56,7 @@ def __init__(self, job_program: Program, job_inputs: list[Any]): self._open_jobs.reverse() self._closed_jobs = set() - def next_job(self) -> Optional[Any]: + def next_job(self) -> Any | None: with self._mutex: if self._worker_exception is not None: return None @@ -72,7 +72,7 @@ def _notify_on_termination(self) -> None: if self._is_terminated(): self._termination_event.set() - def get_worker_exception(self) -> Optional[Exception]: + def get_worker_exception(self) -> Exception | None: return self._worker_exception def close_job(self, job: Job) -> None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py index 178c9653c83c6..6ce02f26abffc 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/result_writer/resource_eval/resource_eval_s3.py @@ -1,7 +1,8 @@ from __future__ import annotations import json -from typing import Callable, Final +from collections.abc import Callable +from typing import Final from localstack.services.stepfunctions.asl.component.state.state_execution.state_map.result_writer.resource_eval.resource_eval import ( ResourceEval, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py index ea0aebac7751d..024df1641b264 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py @@ -1,5 +1,4 @@ import copy -from typing import Optional from localstack.aws.api.stepfunctions import ( EvaluationFailedEventDetails, @@ -99,21 +98,21 @@ class StateMap(ExecutionState): - items: Optional[Items] - items_path: Optional[ItemsPath] + items: Items | None + items_path: ItemsPath | None iteration_component: IterationComponent - item_reader: Optional[ItemReader] - item_selector: Optional[ItemSelector] - parameters: Optional[Parameters] + item_reader: ItemReader | None + item_selector: ItemSelector | None + parameters: Parameters | None max_concurrency_decl: MaxConcurrencyDecl tolerated_failure_count_decl: ToleratedFailureCountDecl tolerated_failure_percentage_decl: ToleratedFailurePercentage - result_path: Optional[ResultPath] + result_path: ResultPath | None result_selector: ResultSelector - retry: Optional[RetryDecl] - catch: Optional[CatchDecl] - label: Optional[Label] - result_writer: Optional[ResultWriter] + retry: RetryDecl | None + catch: CatchDecl | None + label: Label | None + result_writer: ResultWriter | None def __init__(self): super(StateMap, self).__init__( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py index 51ef19322cf5e..448f548c2707f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branch_worker.py @@ -1,7 +1,7 @@ import abc import logging import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import Timestamp from localstack.services.stepfunctions.asl.component.program.program import Program @@ -18,7 +18,7 @@ def on_terminated(self, env: Environment): ... _branch_worker_comm: Final[BranchWorkerComm] _program: Final[Program] - _worker_thread: Optional[threading.Thread] + _worker_thread: threading.Thread | None env: Final[Environment] def __init__(self, branch_worker_comm: BranchWorkerComm, program: Program, env: Environment): @@ -43,7 +43,7 @@ def start(self): TMP_THREADS.append(self._worker_thread) self._worker_thread.start() - def stop(self, stop_date: Timestamp, cause: Optional[str], error: Optional[str]) -> None: + def stop(self, stop_date: Timestamp, cause: str | None, error: str | None) -> None: env = self.env if env: try: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py index d9c268e776f66..1fe296550de3c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/branches_decl.py @@ -1,6 +1,6 @@ import datetime import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ExecutionFailedEventDetails, HistoryEventType from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( @@ -26,7 +26,7 @@ class BranchWorkerPool(BranchWorker.BranchWorkerComm): _termination_event: Final[threading.Event] _active_workers_num: int - _terminated_with_error: Optional[ExecutionFailedEventDetails] + _terminated_with_error: ExecutionFailedEventDetails | None def __init__(self, workers_num: int): self._mutex = threading.Lock() @@ -42,7 +42,7 @@ def on_terminated(self, env: Environment): end_program_state: ProgramState = env.program_state() if isinstance(end_program_state, ProgramError): self._terminated_with_error = select_from_typed_dict( - typed_dict=ExecutionFailedEventDetails, obj=end_program_state.error or dict() + typed_dict=ExecutionFailedEventDetails, obj=end_program_state.error or {} ) self._termination_event.set() else: @@ -53,7 +53,7 @@ def on_terminated(self, env: Environment): def wait(self): self._termination_event.wait() - def get_exit_event_details(self) -> Optional[ExecutionFailedEventDetails]: + def get_exit_event_details(self) -> ExecutionFailedEventDetails | None: return self._terminated_with_error @@ -67,7 +67,7 @@ def _eval_body(self, env: Environment) -> None: branch_worker_pool = BranchWorkerPool(workers_num=len(self.programs)) - branch_workers: list[BranchWorker] = list() + branch_workers: list[BranchWorker] = [] for program in self.programs: # Environment frame for this sub process. env_frame: Environment = env.open_inner_frame() @@ -84,7 +84,7 @@ def _eval_body(self, env: Environment) -> None: branch_worker_pool.wait() # Propagate exception if parallel task failed. - exit_event_details: Optional[ExecutionFailedEventDetails] = ( + exit_event_details: ExecutionFailedEventDetails | None = ( branch_worker_pool.get_exit_event_details() ) if exit_event_details is not None: @@ -103,7 +103,7 @@ def _eval_body(self, env: Environment) -> None: ) # Collect the results and return. - result_list = list() + result_list = [] for worker in branch_workers: env_frame = worker.env diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py index ce7c5c42d4109..3a84f1bfc74d8 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py @@ -1,5 +1,4 @@ import copy -from typing import Optional from localstack.aws.api.stepfunctions import HistoryEventType from localstack.services.stepfunctions.asl.component.common.catch.catch_outcome import CatchOutcome @@ -25,7 +24,7 @@ class StateParallel(ExecutionState): # machine object must have fields named States and StartAt, whose meanings are exactly # like those in the top level of a state machine. branches: BranchesDecl - parargs: Optional[Parargs] + parargs: Parargs | None def __init__(self): super().__init__( diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py index 9f59414b844ab..4f331a11ace76 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/lambda_eval_utils.py @@ -1,6 +1,6 @@ import json from json import JSONDecodeError -from typing import IO, Any, Final, Optional, Union +from typing import IO, Any, Final from localstack.aws.api.lambda_ import InvocationResponse from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import ( @@ -18,15 +18,15 @@ class LambdaFunctionErrorException(Exception): - function_error: Final[Optional[str]] + function_error: Final[str | None] payload: Final[str] - def __init__(self, function_error: Optional[str], payload: str): + def __init__(self, function_error: str | None, payload: str): self.function_error = function_error self.payload = payload -def _from_payload(payload_streaming_body: IO[bytes]) -> Union[json, str]: +def _from_payload(payload_streaming_body: IO[bytes]) -> Any | str: """ This method extracts the lambda payload. The payload may be a string or a JSON stringified object. In the first case, this function converts the output into a UTF-8 string, otherwise it parses the @@ -75,7 +75,7 @@ def execute_lambda_function_integration( parameters=parameters, region=region, state_credentials=state_credentials ) - function_error: Optional[str] = invocation_response.get("FunctionError") + function_error: str | None = invocation_response.get("FunctionError") if function_error: payload_json = invocation_response["Payload"] payload_str = json.dumps(payload_json, separators=(",", ":")) @@ -85,12 +85,12 @@ def execute_lambda_function_integration( env.stack.append(response) -def to_payload_type(payload: Any) -> Optional[bytes]: +def to_payload_type(payload: Any) -> bytes | None: if isinstance(payload, bytes): return payload if payload is None: - str_value = to_json_str(dict()) + str_value = to_json_str({}) elif isinstance(payload, str): try: json.loads(payload) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py index ce1d4288d5a5c..4bee75df0ee3f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/resource.py @@ -2,7 +2,7 @@ import abc from itertools import takewhile -from typing import Final, Optional +from typing import Final from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent from localstack.services.stepfunctions.asl.eval.environment import Environment @@ -33,7 +33,7 @@ def __init__( account: str, task_type: str, name: str, - option: Optional[str], + option: str | None, ): self.arn = arn self.partition = partition @@ -137,7 +137,7 @@ class ServiceResource(Resource): service_name: Final[str] api_name: Final[str] api_action: Final[str] - condition: Final[Optional[str]] + condition: Final[str | None] def __init__(self, resource_arn: ResourceARN): super().__init__(resource_arn=resource_arn) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py index c385368c25dc2..1812657450f2e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service.py @@ -4,7 +4,7 @@ import copy import json import logging -from typing import Any, Final, Optional, Union +from typing import Any, Final from botocore.model import ListShape, OperationModel, Shape, StringShape, StructureShape from botocore.response import StreamingBody @@ -147,8 +147,8 @@ def _to_boto_request(self, parameters: dict, structure_shape: StructureShape) -> parameters_bind_keys: list[str] = list(parameters.keys()) for parameter_key in parameters_bind_keys: norm_parameter_key = camel_to_snake_case(parameter_key) - norm_member_bind: Optional[tuple[str, Optional[StructureShape]]] = ( - norm_member_binds.get(norm_parameter_key) + norm_member_bind: tuple[str, StructureShape | None] | None = norm_member_binds.get( + norm_parameter_key ) if norm_member_bind is not None: norm_member_bind_key, norm_member_bind_shape = norm_member_bind @@ -203,19 +203,19 @@ def _from_boto_response(self, response: Any, structure_shape: StructureShape) -> response[norm_response_key] = response_value - def _get_boto_service_name(self, boto_service_name: Optional[str] = None) -> str: + def _get_boto_service_name(self, boto_service_name: str | None = None) -> str: api_name = boto_service_name or self.resource.api_name return self._SERVICE_NAME_SFN_TO_BOTO_OVERRIDES.get(api_name, api_name) - def _get_boto_service_action(self, service_action_name: Optional[str] = None) -> str: + def _get_boto_service_action(self, service_action_name: str | None = None) -> str: api_action = service_action_name or self.resource.api_action return camel_to_snake_case(api_action) def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: boto_service_name = self._get_boto_service_name(boto_service_name=boto_service_name) service_action_name = self._get_boto_service_action(service_action_name=service_action_name) @@ -228,8 +228,8 @@ def _normalise_parameters( def _normalise_response( self, response: Any, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: boto_service_name = self._get_boto_service_name(boto_service_name=boto_service_name) service_action_name = self._get_boto_service_action(service_action_name=service_action_name) @@ -239,7 +239,7 @@ def _normalise_response( if output_shape is not None: self._from_boto_response(response, output_shape) # noqa - def _verify_size_quota(self, env: Environment, value: Union[str, json]) -> None: + def _verify_size_quota(self, env: Environment, value: str | json) -> None: is_within: bool = is_within_size_quota(value) if is_within: return diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py index b4d8c660a8f81..ea9f7ac1f1687 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_api_gateway.py @@ -4,7 +4,7 @@ import json import logging from json import JSONDecodeError -from typing import Any, Final, Optional, TypedDict +from typing import Any, Final, TypedDict from urllib.parse import urlencode, urljoin, urlparse import requests @@ -76,13 +76,13 @@ class AuthType(str): class TaskParameters(TypedDict): ApiEndpoint: ApiEndpoint Method: Method - Headers: Optional[Headers] - Stage: Optional[Stage] - Path: Optional[Path] - QueryParameters: Optional[QueryParameters] - RequestBody: Optional[RequestBody] - AllowNullValues: Optional[AllowNullValues] - AuthType: Optional[AuthType] + Headers: Headers | None + Stage: Stage | None + Path: Path | None + QueryParameters: QueryParameters | None + RequestBody: RequestBody | None + AllowNullValues: AllowNullValues | None + AuthType: AuthType | None class InvokeOutput(TypedDict): @@ -131,14 +131,14 @@ class StateTaskServiceApiGateway(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return self._SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: # ApiGateway does not support botocore request relay. pass @@ -146,14 +146,14 @@ def _normalise_parameters( def _normalise_response( self, response: Any, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: # ApiGateway does not support botocore request relay. pass @staticmethod - def _query_parameters_of(parameters: TaskParameters) -> Optional[str]: + def _query_parameters_of(parameters: TaskParameters) -> str | None: query_str = None query_parameters = parameters.get("QueryParameters") # TODO: add support for AllowNullValues. @@ -167,8 +167,8 @@ def _query_parameters_of(parameters: TaskParameters) -> Optional[str]: return query_str @staticmethod - def _headers_of(parameters: TaskParameters) -> Optional[dict]: - headers = parameters.get("Headers", dict()) + def _headers_of(parameters: TaskParameters) -> dict | None: + headers = parameters.get("Headers", {}) if headers: for key in headers.keys(): # TODO: the following check takes place at parse time. @@ -239,8 +239,8 @@ def _invoke_output_of(response: Response) -> InvokeOutput: response_body = response.json() except JSONDecodeError: response_body = response.text - if response_body == json.dumps(dict()): - response_body = dict() + if response_body == json.dumps({}): + response_body = {} # since we are not using a case-insensitive dict, and we want to remove a header, for server # compatibility we should consider both casing variants diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py index aff2642e29710..04fbc7bf5bbea 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_aws_sdk.py @@ -132,7 +132,7 @@ def _eval_service_task( region=resource_runtime_part.region, state_credentials=state_credentials, ) - response = getattr(api_client, api_action)(**normalised_parameters) or dict() + response = getattr(api_client, api_action)(**normalised_parameters) or {} if response: response.pop("ResponseMetadata", None) env.stack.append(response) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py index bc83e1f327121..1ed16a236ce4e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_batch.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from botocore.exceptions import ClientError from moto.batch.utils import JobStatus @@ -62,7 +63,7 @@ class StateTaskServiceBatch(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) @staticmethod @@ -70,12 +71,12 @@ def _attach_aws_environment_variables(parameters: dict) -> None: # Attaches to the ContainerOverrides environment variables the AWS managed flags. container_overrides = parameters.get("ContainerOverrides") if container_overrides is None: - container_overrides = dict() + container_overrides = {} parameters["ContainerOverrides"] = container_overrides environment = container_overrides.get("Environment") if environment is None: - environment = list() + environment = [] container_overrides["Environment"] = environment environment.append( @@ -117,7 +118,7 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: "Proxy: null", ] ) - cause = f"Error executing request, Exception : {error_message}, RequestId: {request_id} ({response_details})" + cause = f"{error_message} ({response_details})" return FailureEvent( env=env, error_name=CustomErrorName(error_name), @@ -139,7 +140,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: batch_client = boto_client_for( service="batch", region=resource_runtime_part.region, @@ -148,7 +149,7 @@ def _build_sync_resolver( submission_output: dict = env.stack.pop() job_id = submission_output["JobId"] - def _sync_resolver() -> Optional[dict]: + def _sync_resolver() -> dict | None: describe_jobs_response = batch_client.describe_jobs(jobs=[job_id]) describe_jobs = describe_jobs_response["jobs"] if describe_jobs: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py index bed6e8b78fdd5..3d219e77bed0b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_callback.py @@ -2,7 +2,8 @@ import json import threading import time -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from localstack.aws.api.stepfunctions import ( HistoryEventExecutionDataDetails, @@ -67,7 +68,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: raise RuntimeError( f"Unsupported .sync callback procedure in resource {self.resource.resource_arn}" ) @@ -78,7 +79,7 @@ def _build_sync2_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: raise RuntimeError( f"Unsupported .sync2 callback procedure in resource {self.resource.resource_arn}" ) @@ -88,9 +89,9 @@ def _eval_wait_for_task_token( env: Environment, timeout_seconds: int, callback_endpoint: CallbackEndpoint, - heartbeat_endpoint: Optional[HeartbeatEndpoint], + heartbeat_endpoint: HeartbeatEndpoint | None, ) -> CallbackOutcome: - outcome: Optional[CallbackOutcome] + outcome: CallbackOutcome | None if heartbeat_endpoint is not None: outcome = self._wait_for_task_token_heartbeat( env, callback_endpoint, heartbeat_endpoint @@ -104,12 +105,12 @@ def _eval_wait_for_task_token( def _eval_sync( self, env: Environment, - sync_resolver: Callable[[], Optional[Any]], - timeout_seconds: Optional[int], - callback_endpoint: Optional[CallbackEndpoint], - heartbeat_endpoint: Optional[HeartbeatEndpoint], + sync_resolver: Callable[[], Any | None], + timeout_seconds: int | None, + callback_endpoint: CallbackEndpoint | None, + heartbeat_endpoint: HeartbeatEndpoint | None, ) -> CallbackOutcome | Any: - callback_output: Optional[CallbackOutcome] = None + callback_output: CallbackOutcome | None = None # Listen for WaitForTaskToken signals if an endpoint is provided. if callback_endpoint is not None: @@ -134,7 +135,7 @@ def _local_update_wait_for_task_token(): # an exception in this thread will invalidate env, and therefore the worker thread. # hence why here there are no explicit stopping logic for thread_wait_for_task_token. - sync_result: Optional[Any] = None + sync_result: Any | None = None while env.is_running(): sync_result = sync_resolver() if callback_output or sync_result: @@ -154,7 +155,7 @@ def _eval_integration_pattern( task_output = env.stack.pop() # Initialise the waitForTaskToken Callback endpoint for this task if supported. - callback_endpoint: Optional[CallbackEndpoint] = None + callback_endpoint: CallbackEndpoint | None = None if ResourceCondition.WaitForTaskToken in self._supported_integration_patterns: callback_id = env.states.context_object.context_object_data["Task"]["Token"] callback_endpoint = env.callback_pool_manager.get(callback_id) @@ -164,7 +165,7 @@ def _eval_integration_pattern( timeout_seconds = env.stack.pop() # Setup resources for heartbeat workloads if necessary. - heartbeat_endpoint: Optional[HeartbeatEndpoint] = None + heartbeat_endpoint: HeartbeatEndpoint | None = None if self.heartbeat: self.heartbeat.eval(env=env) heartbeat_seconds = env.stack.pop() @@ -240,7 +241,7 @@ def _wait_for_task_token_timeout( # noqa self, timeout_seconds: int, callback_endpoint: CallbackEndpoint, - ) -> Optional[CallbackOutcome]: + ) -> CallbackOutcome | None: # Awaits a callback notification and returns the outcome received. # If the operation times out or is interrupted it returns None. @@ -249,7 +250,7 @@ def _wait_for_task_token_timeout( # noqa # discarded by the main process. # Note: although this is the same timeout value, this can only decay strictly after the first timeout # started as it is invoked strictly later. - outcome: Optional[CallbackOutcome] = callback_endpoint.wait(timeout=timeout_seconds) + outcome: CallbackOutcome | None = callback_endpoint.wait(timeout=timeout_seconds) return outcome def _wait_for_task_token_heartbeat( # noqa @@ -257,7 +258,7 @@ def _wait_for_task_token_heartbeat( # noqa env: Environment, callback_endpoint: CallbackEndpoint, heartbeat_endpoint: HeartbeatEndpoint, - ) -> Optional[CallbackOutcome]: + ) -> CallbackOutcome | None: outcome = None while ( env.is_running() @@ -285,7 +286,7 @@ def _get_callback_outcome_failure_event( self, env: Environment, ex: CallbackOutcomeFailureError ) -> FailureEvent: callback_outcome_failure: CallbackOutcomeFailure = ex.callback_outcome_failure - error: Optional[str] = callback_outcome_failure.error + error: str | None = callback_outcome_failure.error return FailureEvent( env=env, error_name=CustomErrorName(error_name=error), diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py index 9fb484abc6362..95ebfd4edb8fc 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_dynamodb.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from botocore.exceptions import ClientError @@ -76,7 +76,7 @@ class StateTaskServiceDynamoDB(StateTaskService): - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) @staticmethod diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py index 3b3473aaa848c..2a4395872e877 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_ecs.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from localstack.services.stepfunctions.asl.component.state.state_execution.state_task.credentials import ( StateCredentials, @@ -42,7 +43,7 @@ class StateTaskServiceEcs(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _before_eval_execution( @@ -102,7 +103,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: ecs_client = boto_client_for( service="ecs", region=resource_runtime_part.region, @@ -112,7 +113,7 @@ def _build_sync_resolver( task_arn: str = submission_output["Tasks"][0]["TaskArn"] cluster_arn: str = submission_output["Tasks"][0]["ClusterArn"] - def _sync_resolver() -> Optional[dict]: + def _sync_resolver() -> dict | None: describe_tasks_output = ecs_client.describe_tasks(cluster=cluster_arn, tasks=[task_arn]) last_status: str = describe_tasks_output["tasks"][0]["lastStatus"] diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py index 19640f84ab02f..32416bc7b241d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_events.py @@ -1,5 +1,5 @@ import json -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( @@ -33,9 +33,9 @@ class SfnFailedEntryCountException(RuntimeError): - cause: Final[Optional[dict]] + cause: Final[dict | None] - def __init__(self, cause: Optional[dict]): + def __init__(self, cause: dict | None): super().__init__(json.dumps(cause)) self.cause = cause @@ -44,7 +44,7 @@ class StateTaskServiceEvents(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py index f66a00e26d4ef..a5bf7a5b44642 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_glue.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final import boto3 from botocore.exceptions import ClientError @@ -73,7 +74,7 @@ ] # The type of (sync)handler builder function for StateTaskServiceGlue objects. _API_ACTION_HANDLER_BUILDER_TYPE = Callable[ - [Environment, ResourceRuntimePart, dict, StateCredentials], Callable[[], Optional[Any]] + [Environment, ResourceRuntimePart, dict, StateCredentials], Callable[[], Any | None] ] @@ -81,7 +82,7 @@ class StateTaskServiceGlue(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _get_api_action_handler(self) -> _API_ACTION_HANDLER_TYPE: @@ -174,7 +175,7 @@ def _sync_to_start_job_run( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: # Poll the job run state from glue, using GetJobRun until the job has terminated. Hence, append the output # of GetJobRun to the state. @@ -188,7 +189,7 @@ def _sync_to_start_job_run( resource_runtime_part=resource_runtime_part, state_credentials=state_credentials ) - def _sync_resolver() -> Optional[Any]: + def _sync_resolver() -> Any | None: # Sample GetJobRun until completion. get_job_run_response: dict = glue_client.get_job_run(JobName=job_name, RunId=job_run_id) job_run: dict = get_job_run_response["JobRun"] @@ -232,7 +233,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: sync_resolver_builder = self._get_api_action_sync_builder_handler() sync_resolver = sync_resolver_builder( env, resource_runtime_part, normalised_parameters, state_credentials diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py index 8feebfa1cdc29..4a2b67d208b6e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_lambda.py @@ -1,6 +1,6 @@ import json import logging -from typing import Final, Optional +from typing import Final from botocore.exceptions import ClientError @@ -50,7 +50,7 @@ class StateTaskServiceLambda(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) @staticmethod @@ -105,8 +105,8 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: # Run Payload value casting before normalisation. if "Payload" in parameters: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py index 33bafc723a00e..9fcd18163fde3 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sfn.py @@ -1,5 +1,6 @@ import json -from typing import Any, Callable, Final, Optional +from collections.abc import Callable +from typing import Any, Final from botocore.exceptions import ClientError @@ -52,7 +53,7 @@ class StateTaskServiceSfn(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: @@ -91,8 +92,8 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: def _normalise_parameters( self, parameters: dict, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: if service_action_name is None: if self._get_boto_service_action() == "start_execution": @@ -115,7 +116,7 @@ def _build_sync_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: sfn_client = boto_client_for( service="stepfunctions", region=resource_runtime_part.region, @@ -124,7 +125,7 @@ def _build_sync_resolver( submission_output: dict = env.stack.pop() execution_arn: str = submission_output["ExecutionArn"] - def _sync_resolver() -> Optional[Any]: + def _sync_resolver() -> Any | None: describe_execution_output = sfn_client.describe_execution(executionArn=execution_arn) describe_execution_output: DescribeExecutionOutput = select_from_typed_dict( DescribeExecutionOutput, describe_execution_output @@ -176,7 +177,7 @@ def _build_sync2_resolver( resource_runtime_part: ResourceRuntimePart, normalised_parameters: dict, state_credentials: StateCredentials, - ) -> Callable[[], Optional[Any]]: + ) -> Callable[[], Any | None]: sfn_client = boto_client_for( region=resource_runtime_part.region, service="stepfunctions", @@ -185,7 +186,7 @@ def _build_sync2_resolver( submission_output: dict = env.stack.pop() execution_arn: str = submission_output["ExecutionArn"] - def _sync2_resolver() -> Optional[Any]: + def _sync2_resolver() -> Any | None: describe_execution_output = sfn_client.describe_execution(executionArn=execution_arn) describe_execution_output: DescribeExecutionOutput = select_from_typed_dict( DescribeExecutionOutput, describe_execution_output diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py index 45c6693d0dafd..e230cd418b898 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sns.py @@ -1,4 +1,4 @@ -from typing import Final, Optional +from typing import Final from botocore.exceptions import ClientError @@ -46,7 +46,7 @@ class StateTaskServiceSns(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py index 836cb8ad1b95b..621a43a1a651d 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/service/state_task_service_sqs.py @@ -1,4 +1,4 @@ -from typing import Any, Final, Optional +from typing import Any, Final from botocore.exceptions import ClientError @@ -45,7 +45,7 @@ class StateTaskServiceSqs(StateTaskServiceCallback): def __init__(self): super().__init__(supported_integration_patterns=_SUPPORTED_INTEGRATION_PATTERNS) - def _get_supported_parameters(self) -> Optional[set[str]]: + def _get_supported_parameters(self) -> set[str] | None: return _SUPPORTED_API_PARAM_BINDINGS.get(self.resource.api_action.lower()) def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: @@ -70,8 +70,8 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: def _normalise_response( self, response: Any, - boto_service_name: Optional[str] = None, - service_action_name: Optional[str] = None, + boto_service_name: str | None = None, + service_action_name: str | None = None, ) -> None: super()._normalise_response( response=response, diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py index 79c5f496d7bf8..e2645f2492147 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py @@ -31,8 +31,8 @@ class StateTask(ExecutionState, abc.ABC): resource: Resource - parargs: Optional[Parargs] - credentials: Optional[Credentials] + parargs: Parargs | None + credentials: Credentials | None def __init__(self): super(StateTask, self).__init__( @@ -51,7 +51,7 @@ def _get_supported_parameters(self) -> Optional[set[str]]: # noqa def _eval_parameters(self, env: Environment) -> dict: # Eval raw parameters. - parameters = dict() + parameters = {} if self.parargs is not None: self.parargs.eval(env=env) parameters = env.stack.pop() diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py index d33fc290b611e..e7ed09aad112e 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task_lambda.py @@ -1,6 +1,6 @@ import json import logging -from typing import Union +from typing import Any from botocore.exceptions import ClientError @@ -94,7 +94,7 @@ def _from_error(self, env: Environment, ex: Exception) -> FailureEvent: ), ) - def _verify_size_quota(self, env: Environment, value: Union[str, json]) -> None: + def _verify_size_quota(self, env: Environment, value: str | Any) -> None: is_within: bool = is_within_size_quota(value=value) if is_within: return diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py index 608b27f2044fc..a364ad306b395 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.aws.api.stepfunctions import HistoryEventType, TaskFailedEventDetails from localstack.services.stepfunctions.asl.component.common.error_name.custom_error_name import ( CustomErrorName, @@ -22,8 +20,8 @@ def __init__(self): state_entered_event_type=HistoryEventType.FailStateEntered, state_exited_event_type=None, ) - self.cause: Optional[CauseDecl] = None - self.error: Optional[ErrorDecl] = None + self.cause: CauseDecl | None = None + self.error: ErrorDecl | None = None def from_state_props(self, state_props: StateProps) -> None: super(StateFail, self).from_state_props(state_props) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py index 3a13b935b73ac..b73819a060e05 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.aws.api.stepfunctions import ( HistoryEventType, ) @@ -21,17 +19,17 @@ def __init__(self): # Result (Optional) # Refers to the output of a virtual state_task that is passed on to the next state. If you include the ResultPath # field in your state machine definition, Result is placed as specified by ResultPath and passed on to the - self.result: Optional[Result] = None + self.result: Result | None = None # ResultPath (Optional) # Specifies where to place the output (relative to the input) of the virtual state_task specified in Result. The input # is further filtered as specified by the OutputPath field (if present) before being used as the state's output. - self.result_path: Optional[ResultPath] = None + self.result_path: ResultPath | None = None # Parameters (Optional) # Creates a collection of key-value pairs that will be passed as input. You can specify Parameters as a static # value or select from the input using a path. - self.parameters: Optional[Parameters] = None + self.parameters: Parameters | None = None def from_state_props(self, state_props: StateProps) -> None: super(StatePass, self).from_state_props(state_props) diff --git a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py index f26583bf77d10..9b771fa8784f7 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py +++ b/localstack-core/localstack/services/stepfunctions/asl/component/state/state_wait/wait_function/timestamp.py @@ -1,6 +1,6 @@ import datetime import re -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ExecutionFailedEventDetails, HistoryEventType from localstack.services.stepfunctions.asl.component.common.error_name.failure_event import ( @@ -47,7 +47,7 @@ def _is_valid_timestamp_pattern(timestamp: str) -> bool: return re.match(TIMESTAMP_PATTERN, timestamp) is not None @staticmethod - def _from_timestamp_string(timestamp: str) -> Optional[datetime]: + def _from_timestamp_string(timestamp: str) -> datetime.datetime | None: if not Timestamp._is_valid_timestamp_pattern(timestamp): return None try: diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py b/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py index c5c27a05f4723..5a21ac217df04 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/callback/callback.py @@ -1,7 +1,7 @@ import abc from collections import OrderedDict from threading import Event, Lock -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ActivityDoesNotExist, Arn from localstack.services.stepfunctions.backend.activity import Activity, ActivityTask @@ -26,10 +26,10 @@ def __init__(self, callback_id: CallbackId, output: str): class CallbackOutcomeFailure(CallbackOutcome): - error: Final[Optional[str]] - cause: Final[Optional[str]] + error: Final[str | None] + cause: Final[str | None] - def __init__(self, callback_id: CallbackId, error: Optional[str], cause: Optional[str]): + def __init__(self, callback_id: CallbackId, error: str | None, cause: str | None): super().__init__(callback_id=callback_id) self.error = error self.cause = cause @@ -85,20 +85,20 @@ class HeartbeatTimedOut(CallbackConsumerError): class ActivityTaskStartOutcome: - worker_name: Optional[str] + worker_name: str | None - def __init__(self, worker_name: Optional[str] = None): + def __init__(self, worker_name: str | None = None): self.worker_name = worker_name class ActivityTaskStartEndpoint: _next_activity_task_start_event: Final[Event] - _outcome: Optional[ActivityTaskStartOutcome] + _outcome: ActivityTaskStartOutcome | None def __init__(self): self._next_activity_task_start_event = Event() - def wait(self, timeout_seconds: float) -> Optional[ActivityTaskStartOutcome]: + def wait(self, timeout_seconds: float) -> ActivityTaskStartOutcome | None: self._next_activity_task_start_event.wait(timeout=timeout_seconds) return self._outcome @@ -110,9 +110,9 @@ def notify(self, activity_task: ActivityTaskStartOutcome) -> None: class CallbackEndpoint: callback_id: Final[CallbackId] _notify_event: Final[Event] - _outcome: Optional[CallbackOutcome] - consumer_error: Optional[CallbackConsumerError] - _heartbeat_endpoint: Optional[HeartbeatEndpoint] + _outcome: CallbackOutcome | None + consumer_error: CallbackConsumerError | None + _heartbeat_endpoint: HeartbeatEndpoint | None def __init__(self, callback_id: CallbackId): self.callback_id = callback_id @@ -144,11 +144,11 @@ def notify_heartbeat(self) -> bool: self._heartbeat_endpoint.notify() return True - def wait(self, timeout: Optional[float] = None) -> Optional[CallbackOutcome]: + def wait(self, timeout: float | None = None) -> CallbackOutcome | None: self._notify_event.wait(timeout=timeout) return self._outcome - def get_outcome(self) -> Optional[CallbackOutcome]: + def get_outcome(self) -> CallbackOutcome | None: return self._outcome def report(self, consumer_error: CallbackConsumerError) -> None: @@ -170,7 +170,7 @@ def get_activity_input(self) -> str: def get_activity_task_start_endpoint(self) -> ActivityTaskStartEndpoint: return self._activity_task_start_endpoint - def notify_activity_task_start(self, worker_name: Optional[str]) -> None: + def notify_activity_task_start(self, worker_name: str | None) -> None: self._activity_task_start_endpoint.notify(ActivityTaskStartOutcome(worker_name=worker_name)) @@ -196,7 +196,7 @@ def __init__(self, activity_store: dict[Arn, Activity]): self._activity_store = activity_store self._pool = OrderedDict() - def get(self, callback_id: CallbackId) -> Optional[CallbackEndpoint]: + def get(self, callback_id: CallbackId) -> CallbackEndpoint | None: return self._pool.get(callback_id) def add(self, callback_id: CallbackId) -> CallbackEndpoint: @@ -212,7 +212,7 @@ def add_activity_task( if callback_id in self._pool: raise ValueError("Duplicate callback token id value.") - maybe_activity: Optional[Activity] = self._activity_store.get(activity_arn) + maybe_activity: Activity | None = self._activity_store.get(activity_arn) if maybe_activity is None: raise ActivityDoesNotExist() @@ -232,7 +232,7 @@ def notify(self, callback_id: CallbackId, outcome: CallbackOutcome) -> bool: if callback_endpoint is None: return False - consumer_error: Optional[CallbackConsumerError] = callback_endpoint.consumer_error + consumer_error: CallbackConsumerError | None = callback_endpoint.consumer_error if consumer_error is not None: raise CallbackNotifyConsumerError(callback_consumer_error=consumer_error) @@ -244,7 +244,7 @@ def heartbeat(self, callback_id: CallbackId) -> bool: if callback_endpoint is None: return False - consumer_error: Optional[CallbackConsumerError] = callback_endpoint.consumer_error + consumer_error: CallbackConsumerError | None = callback_endpoint.consumer_error if consumer_error is not None: raise CallbackNotifyConsumerError(callback_consumer_error=consumer_error) diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py b/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py index ecb90be5b8d07..958a83641dc02 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/environment.py @@ -41,24 +41,24 @@ class Environment: _state_mutex: Final[threading.RLock()] - _program_state: Optional[ProgramState] + _program_state: ProgramState | None program_state_event: Final[threading.Event()] event_manager: EventManager event_history_context: Final[EventHistoryContext] - cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] + cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] aws_execution_details: Final[AWSExecutionDetails] execution_type: Final[StateMachineType] callback_pool_manager: CallbackPoolManager map_run_record_pool_manager: MapRunRecordPoolManager activity_store: Final[dict[Arn, Activity]] - mock_test_case: Optional[MockTestCase] = None + mock_test_case: MockTestCase | None = None _frames: Final[list[Environment]] _is_frame: bool = False - heap: dict[str, Any] = dict() - stack: list[Any] = list() + heap: dict[str, Any] = {} + stack: list[Any] = [] states: Final[States] variable_store: Final[VariableStore] @@ -68,10 +68,10 @@ def __init__( execution_type: StateMachineType, context: ContextObjectData, event_history_context: EventHistoryContext, - cloud_watch_logging_session: Optional[CloudWatchLoggingSession], + cloud_watch_logging_session: CloudWatchLoggingSession | None, activity_store: dict[Arn, Activity], - variable_store: Optional[VariableStore] = None, - mock_test_case: Optional[MockTestCase] = None, + variable_store: VariableStore | None = None, + mock_test_case: MockTestCase | None = None, ): super(Environment, self).__init__() self._state_mutex = threading.RLock() @@ -91,17 +91,17 @@ def __init__( self.mock_test_case = mock_test_case - self._frames = list() + self._frames = [] self._is_frame = False - self.heap = dict() - self.stack = list() + self.heap = {} + self.stack = [] self.states = States(context=context) self.variable_store = variable_store or VariableStore() @classmethod def as_frame_of( - cls, env: Environment, event_history_frame_cache: Optional[EventHistoryContext] = None + cls, env: Environment, event_history_frame_cache: EventHistoryContext | None = None ) -> Environment: return Environment.as_inner_frame_of( env=env, @@ -114,7 +114,7 @@ def as_inner_frame_of( cls, env: Environment, variable_store: VariableStore, - event_history_frame_cache: Optional[EventHistoryContext] = None, + event_history_frame_cache: EventHistoryContext | None = None, ) -> Environment: # Construct the frame's context object data. context = ContextObjectData( @@ -148,13 +148,13 @@ def as_inner_frame_of( ) frame.callback_pool_manager = env.callback_pool_manager frame.map_run_record_pool_manager = env.map_run_record_pool_manager - frame.heap = dict() + frame.heap = {} frame._program_state = copy.deepcopy(env._program_state) return frame @property - def next_state_name(self) -> Optional[str]: - next_state_name: Optional[str] = None + def next_state_name(self) -> str | None: + next_state_name: str | None = None program_state = self._program_state if isinstance(program_state, ProgramRunning): next_state_name = program_state.next_state_name @@ -173,8 +173,8 @@ def next_state_name(self, next_state_name: str) -> None: ) @property - def next_field_name(self) -> Optional[str]: - next_field_name: Optional[str] = None + def next_field_name(self) -> str | None: + next_field_name: str | None = None program_state = self._program_state if isinstance(program_state, ProgramRunning): next_field_name = program_state.next_field_name @@ -220,7 +220,7 @@ def set_timed_out(self) -> None: self.program_state_event.set() self.program_state_event.clear() - def set_stop(self, stop_date: Timestamp, cause: Optional[str], error: Optional[str]) -> None: + def set_stop(self, stop_date: Timestamp, cause: str | None, error: str | None) -> None: with self._state_mutex: if isinstance(self._program_state, ProgramRunning): self._program_state = ProgramStopped(stop_date=stop_date, cause=cause, error=error) @@ -229,16 +229,14 @@ def set_stop(self, stop_date: Timestamp, cause: Optional[str], error: Optional[s self.program_state_event.set() self.program_state_event.clear() - def open_frame( - self, event_history_context: Optional[EventHistoryContext] = None - ) -> Environment: + def open_frame(self, event_history_context: EventHistoryContext | None = None) -> Environment: with self._state_mutex: frame = self.as_frame_of(env=self, event_history_frame_cache=event_history_context) self._frames.append(frame) return frame def open_inner_frame( - self, event_history_context: Optional[EventHistoryContext] = None + self, event_history_context: EventHistoryContext | None = None ) -> Environment: with self._state_mutex: variable_store = VariableStore.as_inner_scope_of( diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py b/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py index d053ae70e2187..badb613f23dca 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/evaluation_details.py @@ -1,4 +1,4 @@ -from typing import Any, Final, Optional +from typing import Any, Final from localstack.aws.api.stepfunctions import Arn, Definition, LongArn, StateMachineType @@ -18,12 +18,10 @@ class ExecutionDetails: arn: Final[LongArn] name: Final[str] role_arn: Final[Arn] - inpt: Final[Optional[Any]] + inpt: Final[Any | None] start_time: Final[str] - def __init__( - self, arn: LongArn, name: str, role_arn: Arn, inpt: Optional[Any], start_time: str - ): + def __init__(self, arn: LongArn, name: str, role_arn: Arn, inpt: Any | None, start_time: str): self.arn = arn self.name = name self.role_arn = role_arn diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py b/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py index 8a9ea31a47287..09cd0b64a8a5b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/event/event_manager.py @@ -4,7 +4,7 @@ import datetime import logging import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( HistoryEvent, @@ -61,19 +61,19 @@ class EventManager: _mutex: Final[threading.Lock] _event_id_gen: EventIdGenerator _history_event_list: Final[HistoryEventList] - _cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] + _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] - def __init__(self, cloud_watch_logging_session: Optional[CloudWatchLoggingSession] = None): + def __init__(self, cloud_watch_logging_session: CloudWatchLoggingSession | None = None): self._mutex = threading.Lock() self._event_id_gen = EventIdGenerator() - self._history_event_list = list() + self._history_event_list = [] self._cloud_watch_logging_session = cloud_watch_logging_session def add_event( self, context: EventHistoryContext, event_type: HistoryEventType, - event_details: Optional[EventDetails] = None, + event_details: EventDetails | None = None, timestamp: Timestamp = None, update_source_event_id: bool = True, ) -> int: @@ -105,7 +105,7 @@ def add_event( @staticmethod def _get_current_timestamp() -> datetime.datetime: - return datetime.datetime.now(tz=datetime.timezone.utc) + return datetime.datetime.now(tz=datetime.UTC) @staticmethod def _create_history_event( @@ -113,7 +113,7 @@ def _create_history_event( source_event_id: int, event_type: HistoryEventType, timestamp: datetime.datetime, - event_details: Optional[EventDetails], + event_details: EventDetails | None, ) -> HistoryEvent: history_event = HistoryEvent() if event_details is not None: @@ -130,7 +130,7 @@ def _publish_history_event( source_event_id: int, event_type: HistoryEventType, timestamp: datetime.datetime, - event_details: Optional[EventDetails], + event_details: EventDetails | None, ): history_event = self._create_history_event( event_id=event_id, @@ -154,7 +154,7 @@ def _create_history_log( event_type: HistoryEventType, timestamp: datetime.datetime, execution_arn: LongArn, - event_details: Optional[EventDetails], + event_details: EventDetails | None, include_execution_data: bool, ) -> HistoryLog: log = HistoryLog( @@ -184,7 +184,7 @@ def _publish_history_log( source_event_id: int, event_type: HistoryEventType, timestamp: datetime.datetime, - event_details: Optional[EventDetails], + event_details: EventDetails | None, ): # No logging session for this execution. if self._cloud_watch_logging_session is None: diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py b/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py index de504ad2a8255..4471c64d3a062 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/event/logging.py @@ -2,7 +2,7 @@ import logging from datetime import datetime -from typing import Final, NotRequired, Optional, TypedDict +from typing import Final, NotRequired, TypedDict from botocore.client import BaseClient from botocore.exceptions import ClientError @@ -111,7 +111,7 @@ def __init__( @staticmethod def extract_log_arn_parts_from( logging_configuration: LoggingConfiguration, - ) -> Optional[tuple[str, str, str]]: + ) -> tuple[str, str, str] | None: # Returns a tuple with: account_id, region, and log group name if the logging configuration # specifies a valid cloud watch log group arn, none otherwise. @@ -155,7 +155,7 @@ def extract_log_arn_parts_from( def from_logging_configuration( state_machine_arn: LongArn, logging_configuration: LoggingConfiguration, - ) -> Optional[CloudWatchLoggingConfiguration]: + ) -> CloudWatchLoggingConfiguration | None: log_level = logging_configuration.get("level", LogLevel.OFF) if log_level == LogLevel.OFF: return None diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py b/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py index 00f3af00cb82f..f904964fc662b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/program_state.py @@ -1,5 +1,5 @@ import abc -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ExecutionFailedEventDetails, Timestamp @@ -12,16 +12,16 @@ class ProgramEnded(ProgramState): class ProgramStopped(ProgramState): - def __init__(self, stop_date: Timestamp, error: Optional[str], cause: Optional[str]): + def __init__(self, stop_date: Timestamp, error: str | None, cause: str | None): super().__init__() self.stop_date: Timestamp = stop_date - self.error: Optional[str] = error - self.cause: Optional[str] = cause + self.error: str | None = error + self.cause: str | None = cause class ProgramRunning(ProgramState): - _next_state_name: Optional[str] - _next_field_name: Optional[str] + _next_state_name: str | None + _next_field_name: str | None def __init__(self): super().__init__() @@ -53,9 +53,9 @@ def next_field_name(self, next_field_name) -> None: class ProgramError(ProgramState): - error: Final[Optional[ExecutionFailedEventDetails]] + error: Final[ExecutionFailedEventDetails | None] - def __init__(self, error: Optional[ExecutionFailedEventDetails]): + def __init__(self, error: ExecutionFailedEventDetails | None): super().__init__() self.error = error diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/states.py b/localstack-core/localstack/services/stepfunctions/asl/eval/states.py index 295e4149344e7..813fd292b0786 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/states.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/states.py @@ -1,5 +1,5 @@ import copy -from typing import Any, Final, NotRequired, Optional, TypedDict +from typing import Any, Final, NotRequired, TypedDict from localstack.services.stepfunctions.asl.jsonata.jsonata import ( VariableDeclarations, @@ -18,7 +18,7 @@ class ExecutionData(TypedDict): Id: str - Input: Optional[Any] + Input: Any | None Name: str RoleArn: str StartTime: str # Format: ISO 8601. @@ -43,7 +43,7 @@ class ItemData(TypedDict): # Contains the index number for the array item that is being currently processed. Index: int # Contains the array item being processed. - Value: Optional[Any] + Value: Any | None class MapData(TypedDict): @@ -73,8 +73,8 @@ def update_task_token(self) -> str: class StatesData(TypedDict): input: Any context: ContextObjectData - result: NotRequired[Optional[Any]] - errorOutput: NotRequired[Optional[Any]] + result: NotRequired[Any | None] + errorOutput: NotRequired[Any | None] class States: @@ -87,7 +87,7 @@ def __init__(self, context: ContextObjectData): self.context_object = ContextObject(context_object=context) @staticmethod - def _extract(query: Optional[str], data: Any) -> Any: + def _extract(query: str | None, data: Any) -> Any: if query is None: result = data else: @@ -100,7 +100,7 @@ def extract(self, query: str) -> Any: jsonpath_states_query = "$." + query[1:] return self._extract(jsonpath_states_query, self._states_data) - def get_input(self, query: Optional[str] = None) -> Any: + def get_input(self, query: str | None = None) -> Any: return self._extract(query, self._states_data["input"]) def reset(self, input_value: Any) -> None: @@ -109,10 +109,10 @@ def reset(self, input_value: Any) -> None: self._states_data["result"] = None self._states_data["errorOutput"] = None - def get_context(self, query: Optional[str] = None) -> Any: + def get_context(self, query: str | None = None) -> Any: return self._extract(query, self._states_data["context"]) - def get_result(self, query: Optional[str] = None) -> Any: + def get_result(self, query: str | None = None) -> Any: if "result" not in self._states_data: raise RuntimeError("Illegal access to $states.result") return self._extract(query, self._states_data["result"]) @@ -121,7 +121,7 @@ def set_result(self, result: Any) -> Any: clone_result = copy.deepcopy(result) self._states_data["result"] = clone_result - def get_error_output(self, query: Optional[str] = None) -> Any: + def get_error_output(self, query: str | None = None) -> Any: if "errorOutput" not in self._states_data: raise RuntimeError("Illegal access to $states.errorOutput") return self._extract(query, self._states_data["errorOutput"]) @@ -131,7 +131,7 @@ def set_error_output(self, error_output: Any) -> None: self._states_data["errorOutput"] = clone_error_output def to_variable_declarations( - self, variable_references: Optional[set[VariableReference]] = None + self, variable_references: set[VariableReference] | None = None ) -> VariableDeclarations: if not variable_references or _STATES_PREFIX in variable_references: return encode_jsonata_variable_declarations( @@ -143,7 +143,7 @@ def to_variable_declarations( "result": _STATES_RESULT_PREFIX, "errorOutput": _STATES_ERROR_OUTPUT_PREFIX, } - sub_states = dict() + sub_states = {} for variable_reference in variable_references: if not candidate_sub_states: break diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py b/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py index 8db4b0e427cac..510fa534f1447 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/test_state/environment.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Optional - from localstack.aws.api.stepfunctions import Arn, InspectionData, StateMachineType from localstack.services.stepfunctions.asl.eval.environment import Environment from localstack.services.stepfunctions.asl.eval.evaluation_details import AWSExecutionDetails @@ -32,7 +30,7 @@ def __init__( context: ContextObjectData, event_history_context: EventHistoryContext, activity_store: dict[Arn, Activity], - cloud_watch_logging_session: Optional[CloudWatchLoggingSession] = None, + cloud_watch_logging_session: CloudWatchLoggingSession | None = None, ): super().__init__( aws_execution_details=aws_execution_details, @@ -47,7 +45,7 @@ def __init__( def as_frame_of( cls, env: TestStateEnvironment, - event_history_frame_cache: Optional[EventHistoryContext] = None, + event_history_frame_cache: EventHistoryContext | None = None, ) -> Environment: frame = super().as_frame_of(env=env, event_history_frame_cache=event_history_frame_cache) frame.inspection_data = env.inspection_data @@ -57,7 +55,7 @@ def as_inner_frame_of( cls, env: TestStateEnvironment, variable_store: VariableStore, - event_history_frame_cache: Optional[EventHistoryContext] = None, + event_history_frame_cache: EventHistoryContext | None = None, ) -> Environment: frame = super().as_inner_frame_of( env=env, diff --git a/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py b/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py index 055fb9355ca5c..ff2f0957e825f 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py +++ b/localstack-core/localstack/services/stepfunctions/asl/eval/variable_store.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.asl.jsonata.jsonata import ( VariableDeclarations, @@ -51,12 +51,12 @@ class VariableStore: _declaration_tracing: Final[set[str]] - _outer_variable_declaration_cache: Optional[VariableDeclarations] - _variable_declarations_cache: Optional[VariableDeclarations] + _outer_variable_declaration_cache: VariableDeclarations | None + _variable_declarations_cache: VariableDeclarations | None def __init__(self): - self._outer_scope = dict() - self._inner_scope = dict() + self._outer_scope = {} + self._inner_scope = {} self._declaration_tracing = set() self._outer_variable_declaration_cache = None self._variable_declarations_cache = None @@ -73,7 +73,7 @@ def reset_tracing(self) -> None: # TODO: add typing when this available in service init. def get_assigned_variables(self) -> dict[str, str]: - assigned_variables: dict[str, str] = dict() + assigned_variables: dict[str, str] = {} for traced_declaration_identifier in self._declaration_tracing: traced_declaration_value = self.get(traced_declaration_identifier) if isinstance(traced_declaration_value, str): diff --git a/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py b/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py index ad9dd6acd1010..9fa3b91c8c9ab 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py +++ b/localstack-core/localstack/services/stepfunctions/asl/jsonata/jsonata.py @@ -2,8 +2,9 @@ import json import re +from collections.abc import Callable from pathlib import Path -from typing import Any, Callable, Final, Optional +from typing import Any, Final import jpype import jpype.imports @@ -45,9 +46,9 @@ class JSONataException(Exception): error: Final[str] - details: Optional[str] + details: str | None - def __init__(self, error: str, details: Optional[str]): + def __init__(self, error: str, details: str | None): self.error = error self.details = details @@ -105,7 +106,7 @@ def eval_jsonata(self, jsonata_expression: JSONataExpression) -> Any: # Lazy initialization of the `eval_jsonata` function pointer. # This ensures the JVM is only started when JSONata functionality is needed. -_eval_jsonata: Optional[Callable[[JSONataExpression], Any]] = None +_eval_jsonata: Callable[[JSONataExpression], Any] | None = None def eval_jsonata_expression(jsonata_expression: JSONataExpression) -> Any: @@ -144,7 +145,7 @@ def extract_jsonata_variable_references( def encode_jsonata_variable_declarations( bindings: dict[VariableReference, Any], ) -> VariableDeclarations: - declarations_parts: list[str] = list() + declarations_parts: list[str] = [] for variable_reference, value in bindings.items(): if isinstance(value, str): value_str_lit = f'"{value}"' diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py b/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py index 29c9c93f53bf5..30f9a07883072 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/asl_parser.py @@ -15,7 +15,7 @@ class SyntaxErrorListener(ErrorListener): def __init__(self): super().__init__() - self.errors = list() + self.errors = [] def syntaxError(self, recognizer, offending_symbol, line, column, message, exception): log_parts = [f"line {line}:{column}"] diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py b/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py index c25f0345b1b0d..63b3dbeb5423c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/intrinsic/preprocessor.py @@ -1,5 +1,4 @@ import re -from typing import Optional from antlr4.tree.Tree import ParseTree, TerminalNodeImpl @@ -86,9 +85,9 @@ def visitFunc_arg_bool(self, ctx: ASLIntrinsicParser.Func_arg_boolContext) -> Ar return ArgumentLiteral(definition_value=bool_val) def visitFunc_arg_list(self, ctx: ASLIntrinsicParser.Func_arg_listContext) -> ArgumentList: - arguments: list[Argument] = list() + arguments: list[Argument] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, Argument): arguments.append(cmp) return ArgumentList(arguments=arguments) diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py b/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py index 93132888e920b..cb25e4989a4a0 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/preprocessor.py @@ -1,6 +1,6 @@ import json import logging -from typing import Any, Optional +from typing import Any from antlr4 import ParserRuleContext from antlr4.tree.Tree import ParseTree, TerminalNodeImpl @@ -318,7 +318,7 @@ class Preprocessor(ASLParserVisitor): - _query_language_per_scope: list[QueryLanguage] = list() + _query_language_per_scope: list[QueryLanguage] = [] def _get_current_query_language(self) -> QueryLanguage: return self._query_language_per_scope[-1] @@ -376,7 +376,7 @@ def _raise_if_query_language_is_not( ) @staticmethod - def _inner_string_of(parser_rule_context: ParserRuleContext) -> Optional[str]: + def _inner_string_of(parser_rule_context: ParserRuleContext) -> str | None: if is_terminal(parser_rule_context, ASLLexer.NULL): return None inner_str = parser_rule_context.getText() @@ -408,7 +408,7 @@ def visitStartat_decl(self, ctx: ASLParser.Startat_declContext) -> StartAt: def visitStates_decl(self, ctx: ASLParser.States_declContext) -> States: states = States() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, CommonStateField): # TODO move check to setter or checker layer? if cmp.name in states.states: @@ -429,7 +429,7 @@ def visitResource_decl(self, ctx: ASLParser.Resource_declContext) -> Resource: def visitEnd_decl(self, ctx: ASLParser.End_declContext) -> End: bool_child: ParseTree = ctx.children[-1] - bool_term: Optional[TerminalNodeImpl] = is_terminal(bool_child) + bool_term: TerminalNodeImpl | None = is_terminal(bool_child) if bool_term is None: raise ValueError(f"Could not derive End from declaration context '{ctx.getText()}'") bool_term_rule: int = bool_term.getSymbol().type @@ -448,7 +448,7 @@ def visitResult_path_decl(self, ctx: ASLParser.Result_path_declContext) -> Resul return ResultPath(result_path_src=inner_str) def visitInput_path_decl(self, ctx: ASLParser.Input_path_declContext) -> InputPath: - string_sampler: Optional[StringSampler] = None + string_sampler: StringSampler | None = None if not is_terminal(pt=ctx.children[-1], token_type=ASLLexer.NULL): string_sampler: StringSampler = self.visitString_sampler(ctx.string_sampler()) return InputPath(string_sampler=string_sampler) @@ -457,7 +457,7 @@ def visitOutput_path_decl(self, ctx: ASLParser.Output_path_declContext) -> Outpu self._raise_if_query_language_is_not( query_language_mode=QueryLanguageMode.JSONPath, ctx=ctx ) - string_sampler: Optional[StringSampler] = None + string_sampler: StringSampler | None = None if is_production(ctx.children[-1], ASLParser.RULE_string_sampler): string_sampler: StringSampler = self.visitString_sampler(ctx.children[-1]) return OutputPath(string_sampler=string_sampler) @@ -527,7 +527,7 @@ def visitResult_selector_decl( def visitBranches_decl(self, ctx: ASLParser.Branches_declContext) -> BranchesDecl: programs: list[Program] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, Program): programs.append(cmp) return BranchesDecl(programs=programs) @@ -536,7 +536,7 @@ def visitState_decl_body(self, ctx: ASLParser.State_decl_bodyContext) -> StatePr self._open_query_language_scope(ctx) state_props = StateProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) state_props.add(cmp) if state_props.get(QueryLanguage) is None: state_props.add(self._get_current_query_language()) @@ -583,7 +583,7 @@ def _common_state_field_of(state_props: StateProps) -> CommonStateField: def visitCondition_lit(self, ctx: ASLParser.Condition_litContext) -> ConditionJSONataLit: self._raise_if_query_language_is_not(query_language_mode=QueryLanguageMode.JSONata, ctx=ctx) bool_child: ParseTree = ctx.children[-1] - bool_term: Optional[TerminalNodeImpl] = is_terminal(bool_child) + bool_term: TerminalNodeImpl | None = is_terminal(bool_child) if bool_term is None: raise ValueError( f"Could not derive boolean literal from declaration context '{ctx.getText()}'." @@ -651,7 +651,7 @@ def visitChoice_operator( self._raise_if_query_language_is_not( query_language_mode=QueryLanguageMode.JSONPath, ctx=ctx ) - pt: Optional[TerminalNodeImpl] = is_terminal(ctx.children[0]) + pt: TerminalNodeImpl | None = is_terminal(ctx.children[0]) if not pt: raise ValueError(f"Could not derive ChoiceOperator in block '{ctx.getText()}'.") return ComparisonComposite.ChoiceOp(pt.symbol.type) @@ -660,9 +660,9 @@ def visitComparison_composite( self, ctx: ASLParser.Comparison_compositeContext ) -> ComparisonComposite: choice_op: ComparisonComposite.ChoiceOp = self.visit(ctx.choice_operator()) - rules: list[ChoiceRule] = list() + rules: list[ChoiceRule] = [] for child in ctx.children[1:]: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if not cmp: continue elif isinstance(cmp, ChoiceRule): @@ -685,7 +685,7 @@ def visitChoice_rule_comparison_composite( ) -> ChoiceRule: composite_stmts = ComparisonCompositeProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) composite_stmts.add(cmp) return ChoiceRule( comparison=composite_stmts.get( @@ -705,7 +705,7 @@ def visitChoice_rule_comparison_variable( ) -> ChoiceRule: comparison_stmts = StateProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) comparison_stmts.add(cmp) if self._is_query_language(query_language_mode=QueryLanguageMode.JSONPath): variable: Variable = comparison_stmts.get( @@ -746,9 +746,9 @@ def visitChoice_rule_comparison_variable( ) def visitChoices_decl(self, ctx: ASLParser.Choices_declContext) -> ChoicesDecl: - rules: list[ChoiceRule] = list() + rules: list[ChoiceRule] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if not cmp: continue elif isinstance(cmp, ChoiceRule): @@ -966,7 +966,7 @@ def visitCsv_header_location_decl( return CSVHeaderLocation(csv_header_location_value=value) def visitCsv_headers_decl(self, ctx: ASLParser.Csv_headers_declContext) -> CSVHeaders: - csv_headers: list[str] = list() + csv_headers: list[str] = [] for child in ctx.children[3:-1]: maybe_str = is_production(pt=child, rule_index=ASLParser.RULE_string_literal) if maybe_str is not None: @@ -1074,9 +1074,9 @@ def visitResult_writer_decl(self, ctx: ASLParser.Result_writer_declContext) -> R return ResultWriter(resource=resource, parargs=parargs) def visitRetry_decl(self, ctx: ASLParser.Retry_declContext) -> RetryDecl: - retriers: list[RetrierDecl] = list() + retriers: list[RetrierDecl] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, RetrierDecl): retriers.append(cmp) return RetryDecl(retriers=retriers) @@ -1084,7 +1084,7 @@ def visitRetry_decl(self, ctx: ASLParser.Retry_declContext) -> RetryDecl: def visitRetrier_decl(self, ctx: ASLParser.Retrier_declContext) -> RetrierDecl: props = RetrierProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) props.add(cmp) return RetrierDecl.from_retrier_props(props=props) @@ -1092,7 +1092,7 @@ def visitRetrier_stmt(self, ctx: ASLParser.Retrier_stmtContext): return self.visit(ctx.children[0]) def visitError_equals_decl(self, ctx: ASLParser.Error_equals_declContext) -> ErrorEqualsDecl: - error_names: list[ErrorName] = list() + error_names: list[ErrorName] = [] for child in ctx.children: cmp = self.visit(child) if isinstance(cmp, ErrorName): @@ -1103,7 +1103,7 @@ def visitError_name(self, ctx: ASLParser.Error_nameContext) -> ErrorName: pt = ctx.children[0] # Case: StatesErrorName. - prc: Optional[ParserRuleContext] = is_production( + prc: ParserRuleContext | None = is_production( pt=pt, rule_index=ASLParser.RULE_states_error_name ) if prc: @@ -1114,7 +1114,7 @@ def visitError_name(self, ctx: ASLParser.Error_nameContext) -> ErrorName: return CustomErrorName(error_name=error_name) def visitStates_error_name(self, ctx: ASLParser.States_error_nameContext) -> StatesErrorName: - pt: Optional[TerminalNodeImpl] = is_terminal(ctx.children[0]) + pt: TerminalNodeImpl | None = is_terminal(ctx.children[0]) if not pt: raise ValueError(f"Could not derive ErrorName in block '{ctx.getText()}'.") states_error_name_type = StatesErrorNameType(pt.symbol.type) @@ -1140,15 +1140,15 @@ def visitJitter_strategy_decl( self, ctx: ASLParser.Jitter_strategy_declContext ) -> JitterStrategyDecl: last_child: ParseTree = ctx.children[-1] - strategy_child: Optional[TerminalNodeImpl] = is_terminal(last_child) + strategy_child: TerminalNodeImpl | None = is_terminal(last_child) strategy_value = strategy_child.getSymbol().type jitter_strategy = JitterStrategy(strategy_value) return JitterStrategyDecl(jitter_strategy=jitter_strategy) def visitCatch_decl(self, ctx: ASLParser.Catch_declContext) -> CatchDecl: - catchers: list[CatcherDecl] = list() + catchers: list[CatcherDecl] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, CatcherDecl): catchers.append(cmp) return CatchDecl(catchers=catchers) @@ -1156,7 +1156,7 @@ def visitCatch_decl(self, ctx: ASLParser.Catch_declContext) -> CatchDecl: def visitCatcher_decl(self, ctx: ASLParser.Catcher_declContext) -> CatcherDecl: props = CatcherProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) props.add(cmp) if self._is_query_language(QueryLanguageMode.JSONPath) and not props.get(ResultPath): props.add(CatcherDecl.DEFAULT_RESULT_PATH) @@ -1172,7 +1172,7 @@ def visitPayload_value_int(self, ctx: ASLParser.Payload_value_intContext) -> Pay def visitPayload_value_bool(self, ctx: ASLParser.Payload_value_boolContext) -> PayloadValueBool: bool_child: ParseTree = ctx.children[0] - bool_term: Optional[TerminalNodeImpl] = is_terminal(bool_child) + bool_term: TerminalNodeImpl | None = is_terminal(bool_child) if bool_term is None: raise ValueError( f"Could not derive PayloadValueBool from declaration context '{ctx.getText()}'." @@ -1208,17 +1208,17 @@ def visitPayload_binding_value( return PayloadBindingValue(field=string_literal.literal_value, payload_value=payload_value) def visitPayload_arr_decl(self, ctx: ASLParser.Payload_arr_declContext) -> PayloadArr: - payload_values: list[PayloadValue] = list() + payload_values: list[PayloadValue] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, PayloadValue): payload_values.append(cmp) return PayloadArr(payload_values=payload_values) def visitPayload_tmpl_decl(self, ctx: ASLParser.Payload_tmpl_declContext) -> PayloadTmpl: - payload_bindings: list[PayloadBinding] = list() + payload_bindings: list[PayloadBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, PayloadBinding): payload_bindings.append(cmp) return PayloadTmpl(payload_bindings=payload_bindings) @@ -1231,7 +1231,7 @@ def visitProgram_decl(self, ctx: ASLParser.Program_declContext) -> Program: self._open_query_language_scope(ctx) props = TypedProps() for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) props.add(cmp) if props.get(QueryLanguage) is None: props.add(self._get_current_query_language()) @@ -1312,9 +1312,9 @@ def visitAssign_template_value(self, ctx: ASLParser.Assign_template_valueContext def visitAssign_template_value_array( self, ctx: ASLParser.Assign_template_value_arrayContext ) -> AssignTemplateValueArray: - values: list[AssignTemplateValue] = list() + values: list[AssignTemplateValue] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, AssignTemplateValue): values.append(cmp) return AssignTemplateValueArray(values=values) @@ -1322,9 +1322,9 @@ def visitAssign_template_value_array( def visitAssign_template_value_object( self, ctx: ASLParser.Assign_template_value_objectContext ) -> AssignTemplateValueObject: - bindings: list[AssignTemplateBinding] = list() + bindings: list[AssignTemplateBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, AssignTemplateBinding): bindings.append(cmp) return AssignTemplateValueObject(bindings=bindings) @@ -1359,9 +1359,9 @@ def visitAssign_decl_binding( def visitAssign_decl_body( self, ctx: ASLParser.Assign_decl_bodyContext ) -> list[AssignDeclBinding]: - bindings: list[AssignDeclBinding] = list() + bindings: list[AssignDeclBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, AssignDeclBinding): bindings.append(cmp) return bindings @@ -1414,9 +1414,9 @@ def visitJsonata_template_value( def visitJsonata_template_value_array( self, ctx: ASLParser.Jsonata_template_value_arrayContext ) -> JSONataTemplateValueArray: - values: list[JSONataTemplateValue] = list() + values: list[JSONataTemplateValue] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, JSONataTemplateValue): values.append(cmp) return JSONataTemplateValueArray(values=values) @@ -1424,9 +1424,9 @@ def visitJsonata_template_value_array( def visitJsonata_template_value_object( self, ctx: ASLParser.Jsonata_template_value_objectContext ) -> JSONataTemplateValueObject: - bindings: list[JSONataTemplateBinding] = list() + bindings: list[JSONataTemplateBinding] = [] for child in ctx.children: - cmp: Optional[Component] = self.visit(child) + cmp: Component | None = self.visit(child) if isinstance(cmp, JSONataTemplateBinding): bindings.append(cmp) return JSONataTemplateValueObject(bindings=bindings) diff --git a/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py b/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py index fda0913ec86b2..a63bce525e156 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py +++ b/localstack-core/localstack/services/stepfunctions/asl/parse/typed_props.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from typing import Any, Optional +from typing import Any class TypedProps: @@ -18,7 +18,7 @@ def _add(self, typ: type, instance: Any) -> None: ) self._instance_by_type[typ] = instance - def get(self, typ: type, raise_on_missing: Optional[Exception] = None) -> Optional[Any]: + def get(self, typ: type, raise_on_missing: Exception | None = None) -> Any | None: if raise_on_missing and typ not in self._instance_by_type: raise raise_on_missing return self._instance_by_type.get(typ) diff --git a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py index 6c4514183bfa3..e0feefb65c04b 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/intrinsic/variable_names_intrinsic_static_analyser.py @@ -16,7 +16,7 @@ class VariableNamesIntrinsicStaticAnalyser(IntrinsicStaticAnalyser): def __init__(self): super().__init__() - self._variable_names = list() + self._variable_names = [] @staticmethod def process_and_get(definition: str) -> VariableNameList: diff --git a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py index 93edc9a06a97f..cbfd66bddff28 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py +++ b/localstack-core/localstack/services/stepfunctions/asl/static_analyser/variable_references_static_analyser.py @@ -30,7 +30,7 @@ def process_and_get(definition: str) -> VariableReferences: def __init__(self): super().__init__() - self._fringe_state_names = list() + self._fringe_state_names = [] self._variable_references = OrderedDict() def get_variable_references(self) -> VariableReferences: @@ -54,7 +54,7 @@ def _put_variable_reference(self, variable_reference: VariableReference) -> None def _put_variable_name(self, variable_name: VariableName) -> None: state_name = self._fringe_state_names[-1] - variable_name_list: VariableNameList = self._variable_references.get(state_name, list()) + variable_name_list: VariableNameList = self._variable_references.get(state_name, []) if variable_name in variable_name_list: return variable_name_list.append(variable_name) diff --git a/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py b/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py index 893db6cc28f44..1ea102727a29c 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py +++ b/localstack-core/localstack/services/stepfunctions/asl/utils/encoding.py @@ -1,7 +1,7 @@ import datetime import json from json import JSONEncoder -from typing import Any, Optional +from typing import Any class _DateTimeEncoder(JSONEncoder): @@ -12,5 +12,5 @@ def default(self, obj): return str(obj) -def to_json_str(obj: Any, separators: Optional[tuple[str, str]] = None) -> str: +def to_json_str(obj: Any, separators: tuple[str, str] | None = None) -> str: return json.dumps(obj, cls=_DateTimeEncoder, separators=separators) diff --git a/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py b/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py index 2447458683daf..592f09c76cf48 100644 --- a/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py +++ b/localstack-core/localstack/services/stepfunctions/asl/utils/json_path.py @@ -1,5 +1,5 @@ import re -from typing import Any, Final, Optional +from typing import Any, Final from jsonpath_ng.ext import parse from jsonpath_ng.jsonpath import Index @@ -24,7 +24,7 @@ def _contains_slice_or_wildcard_array(path: str) -> bool: class NoSuchJsonPathError(Exception): json_path: Final[str] data: Final[Any] - _message: Optional[str] + _message: str | None def __init__(self, json_path: str, data: Any): self.json_path = json_path diff --git a/localstack-core/localstack/services/stepfunctions/backend/activity.py b/localstack-core/localstack/services/stepfunctions/backend/activity.py index 8800dbd3fa122..cabb8f98cd420 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/activity.py +++ b/localstack-core/localstack/services/stepfunctions/backend/activity.py @@ -1,6 +1,6 @@ import datetime from collections import deque -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( ActivityListItem, @@ -26,16 +26,16 @@ class Activity: creation_date: Final[Timestamp] _tasks: Final[deque[ActivityTask]] - def __init__(self, arn: Arn, name: Name, creation_date: Optional[Timestamp] = None): + def __init__(self, arn: Arn, name: Name, creation_date: Timestamp | None = None): self.arn = arn self.name = name - self.creation_date = creation_date or datetime.datetime.now(tz=datetime.timezone.utc) + self.creation_date = creation_date or datetime.datetime.now(tz=datetime.UTC) self._tasks = deque() def add_task(self, task: ActivityTask): self._tasks.append(task) - def get_task(self) -> Optional[ActivityTask]: + def get_task(self) -> ActivityTask | None: return self._tasks.popleft() def to_describe_activity_output(self) -> DescribeActivityOutput: diff --git a/localstack-core/localstack/services/stepfunctions/backend/alias.py b/localstack-core/localstack/services/stepfunctions/backend/alias.py index 155890abf4cb3..b66574c9a4128 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/alias.py +++ b/localstack-core/localstack/services/stepfunctions/backend/alias.py @@ -4,7 +4,7 @@ import datetime import random import threading -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( AliasDescription, @@ -20,9 +20,9 @@ class Alias: _mutex: Final[threading.Lock] - update_date: Optional[datetime.datetime] + update_date: datetime.datetime | None name: Final[CharacterRestrictedName] - _description: Optional[AliasDescription] + _description: AliasDescription | None _routing_configuration_list: RoutingConfigurationList _state_machine_version_arns: list[Arn] _execution_probability_distribution: list[int] @@ -34,7 +34,7 @@ def __init__( self, state_machine_arn: Arn, name: CharacterRestrictedName, - description: Optional[AliasDescription], + description: AliasDescription | None, routing_configuration_list: RoutingConfigurationList, ): self._mutex = threading.Lock() @@ -66,7 +66,7 @@ def is_idempotent(self, other: Alias) -> bool: @staticmethod def _get_mutex_date() -> datetime.datetime: - return datetime.datetime.now(tz=datetime.timezone.utc) + return datetime.datetime.now(tz=datetime.UTC) def get_routing_configuration_list(self) -> RoutingConfigurationList: return copy.deepcopy(self._routing_configuration_list) @@ -77,7 +77,7 @@ def is_router_for(self, state_machine_version_arn: Arn) -> bool: def update( self, - description: Optional[AliasDescription], + description: AliasDescription | None, routing_configuration_list: RoutingConfigurationList, ) -> None: with self._mutex: @@ -88,8 +88,8 @@ def update( if routing_configuration_list: self._routing_configuration_list = routing_configuration_list - self._state_machine_version_arns = list() - self._execution_probability_distribution = list() + self._state_machine_version_arns = [] + self._execution_probability_distribution = [] for routing_configuration in routing_configuration_list: self._state_machine_version_arns.append( routing_configuration["stateMachineVersionArn"] diff --git a/localstack-core/localstack/services/stepfunctions/backend/execution.py b/localstack-core/localstack/services/stepfunctions/backend/execution.py index 552497557193f..9cb08023ca593 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/execution.py +++ b/localstack-core/localstack/services/stepfunctions/backend/execution.py @@ -3,7 +3,7 @@ import datetime import json import logging -from typing import Final, Optional +from typing import Final from localstack.aws.api.events import PutEventsRequestEntry from localstack.aws.api.stepfunctions import ( @@ -72,7 +72,7 @@ def __init__(self, execution: Execution): def _reflect_execution_status(self): exit_program_state: ProgramState = self.execution.exec_worker.env.program_state() - self.execution.stop_date = datetime.datetime.now(tz=datetime.timezone.utc) + self.execution.stop_date = datetime.datetime.now(tz=datetime.UTC) if isinstance(exit_program_state, ProgramEnded): self.execution.exec_status = ExecutionStatus.SUCCEEDED self.execution.output = self.execution.exec_worker.env.states.get_input() @@ -105,27 +105,27 @@ class Execution: state_machine: Final[StateMachineInstance] state_machine_arn: Final[Arn] - state_machine_version_arn: Final[Optional[Arn]] - state_machine_alias_arn: Final[Optional[Arn]] + state_machine_version_arn: Final[Arn | None] + state_machine_alias_arn: Final[Arn | None] - mock_test_case: Final[Optional[MockTestCase]] + mock_test_case: Final[MockTestCase | None] start_date: Final[Timestamp] - input_data: Final[Optional[json]] - input_details: Final[Optional[CloudWatchEventsExecutionDataDetails]] - trace_header: Final[Optional[TraceHeader]] - _cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] + input_data: Final[json | None] + input_details: Final[CloudWatchEventsExecutionDataDetails | None] + trace_header: Final[TraceHeader | None] + _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] - exec_status: Optional[ExecutionStatus] - stop_date: Optional[Timestamp] + exec_status: ExecutionStatus | None + stop_date: Timestamp | None - output: Optional[json] - output_details: Optional[CloudWatchEventsExecutionDataDetails] + output: json | None + output_details: CloudWatchEventsExecutionDataDetails | None - error: Optional[SensitiveError] - cause: Optional[SensitiveCause] + error: SensitiveError | None + cause: SensitiveCause | None - exec_worker: Optional[ExecutionWorker] + exec_worker: ExecutionWorker | None _activity_store: dict[Arn, Activity] @@ -139,12 +139,12 @@ def __init__( region_name: str, state_machine: StateMachineInstance, start_date: Timestamp, - cloud_watch_logging_session: Optional[CloudWatchLoggingSession], + cloud_watch_logging_session: CloudWatchLoggingSession | None, activity_store: dict[Arn, Activity], - input_data: Optional[json] = None, - trace_header: Optional[TraceHeader] = None, - state_machine_alias_arn: Optional[Arn] = None, - mock_test_case: Optional[MockTestCase] = None, + input_data: json | None = None, + trace_header: TraceHeader | None = None, + state_machine_alias_arn: Arn | None = None, + mock_test_case: MockTestCase | None = None, ): self.name = name self.sm_type = sm_type @@ -258,7 +258,7 @@ def to_execution_list_item(self) -> ExecutionListItem: def to_history_output(self) -> GetExecutionHistoryOutput: env = self.exec_worker.env - event_history: HistoryEventList = list() + event_history: HistoryEventList = [] if env is not None: # The execution has not started yet. event_history: HistoryEventList = env.event_manager.get_event_history() @@ -267,9 +267,7 @@ def to_history_output(self) -> GetExecutionHistoryOutput: @staticmethod def _to_serialized_date(timestamp: datetime.datetime) -> str: """See test in tests.aws.services.stepfunctions.v2.base.test_base.TestSnfBase.test_execution_dateformat""" - return ( - f"{timestamp.astimezone(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}Z" - ) + return f"{timestamp.astimezone(datetime.UTC).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}Z" def _get_start_execution_worker_comm(self) -> BaseExecutionWorkerCommunication: return BaseExecutionWorkerCommunication(self) @@ -318,14 +316,14 @@ def start(self) -> None: self.publish_execution_status_change_event() self.exec_worker.start() - def stop(self, stop_date: datetime.datetime, error: Optional[str], cause: Optional[str]): - exec_worker: Optional[ExecutionWorker] = self.exec_worker + def stop(self, stop_date: datetime.datetime, error: str | None, cause: str | None): + exec_worker: ExecutionWorker | None = self.exec_worker if exec_worker: exec_worker.stop(stop_date=stop_date, cause=cause, error=error) def publish_execution_status_change_event(self): input_value = ( - dict() if not self.input_data else to_json_str(self.input_data, separators=(",", ":")) + {} if not self.input_data else to_json_str(self.input_data, separators=(",", ":")) ) output_value = ( None if self.output is None else to_json_str(self.output, separators=(",", ":")) @@ -380,7 +378,7 @@ def _reflect_execution_status(self) -> None: class SyncExecution(Execution): - sync_execution_status: Optional[SyncExecutionStatus] = None + sync_execution_status: SyncExecutionStatus | None = None def _get_start_execution_worker(self) -> SyncExecutionWorker: return SyncExecutionWorker( diff --git a/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py b/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py index c2d14c2085295..c3f382c883a71 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py +++ b/localstack-core/localstack/services/stepfunctions/backend/execution_worker.py @@ -1,6 +1,6 @@ import datetime from threading import Thread -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( Arn, @@ -36,19 +36,19 @@ class ExecutionWorker: _evaluation_details: Final[EvaluationDetails] _execution_communication: Final[ExecutionWorkerCommunication] - _cloud_watch_logging_session: Final[Optional[CloudWatchLoggingSession]] - _mock_test_case: Final[Optional[MockTestCase]] + _cloud_watch_logging_session: Final[CloudWatchLoggingSession | None] + _mock_test_case: Final[MockTestCase | None] _activity_store: dict[Arn, Activity] - env: Optional[Environment] + env: Environment | None def __init__( self, evaluation_details: EvaluationDetails, exec_comm: ExecutionWorkerCommunication, - cloud_watch_logging_session: Optional[CloudWatchLoggingSession], + cloud_watch_logging_session: CloudWatchLoggingSession | None, activity_store: dict[Arn, Activity], - mock_test_case: Optional[MockTestCase] = None, + mock_test_case: MockTestCase | None = None, ): self._evaluation_details = evaluation_details self._execution_communication = exec_comm @@ -113,7 +113,7 @@ def start(self): TMP_THREADS.append(execution_logic_thread) execution_logic_thread.start() - def stop(self, stop_date: datetime.datetime, error: Optional[str], cause: Optional[str]): + def stop(self, stop_date: datetime.datetime, error: str | None, cause: str | None): self.env.set_stop(stop_date=stop_date, cause=cause, error=error) diff --git a/localstack-core/localstack/services/stepfunctions/backend/state_machine.py b/localstack-core/localstack/services/stepfunctions/backend/state_machine.py index 71c82f55c881c..d64e142bbe973 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/state_machine.py +++ b/localstack-core/localstack/services/stepfunctions/backend/state_machine.py @@ -4,7 +4,7 @@ import datetime import json from collections import OrderedDict -from typing import Final, Optional +from typing import Final from localstack.aws.api.stepfunctions import ( Arn, @@ -37,15 +37,15 @@ class StateMachineInstance: name: Name arn: Arn - revision_id: Optional[RevisionId] + revision_id: RevisionId | None definition: Definition role_arn: Arn create_date: datetime.datetime sm_type: StateMachineType logging_config: LoggingConfiguration - cloud_watch_logging_configuration: Optional[CloudWatchLoggingConfiguration] - tags: Optional[TagList] - tracing_config: Optional[TracingConfiguration] + cloud_watch_logging_configuration: CloudWatchLoggingConfiguration | None + tags: TagList | None + tracing_config: TracingConfiguration | None def __init__( self, @@ -54,18 +54,18 @@ def __init__( definition: Definition, role_arn: Arn, logging_config: LoggingConfiguration, - cloud_watch_logging_configuration: Optional[CloudWatchLoggingConfiguration] = None, - create_date: Optional[datetime.datetime] = None, - sm_type: Optional[StateMachineType] = None, - tags: Optional[TagList] = None, - tracing_config: Optional[TracingConfiguration] = None, + cloud_watch_logging_configuration: CloudWatchLoggingConfiguration | None = None, + create_date: datetime.datetime | None = None, + sm_type: StateMachineType | None = None, + tags: TagList | None = None, + tracing_config: TracingConfiguration | None = None, ): self.name = name self.arn = arn self.revision_id = None self.definition = definition self.role_arn = role_arn - self.create_date = create_date or datetime.datetime.now(tz=datetime.timezone.utc) + self.create_date = create_date or datetime.datetime.now(tz=datetime.UTC) self.sm_type = sm_type or StateMachineType.STANDARD self.logging_config = logging_config self.cloud_watch_logging_configuration = cloud_watch_logging_configuration @@ -106,7 +106,7 @@ def __init__( arn: Arn, definition: Definition, role_arn: Arn, - create_date: Optional[datetime.datetime] = None, + create_date: datetime.datetime | None = None, ): super().__init__( name, @@ -125,7 +125,7 @@ def itemise(self): class TagManager: - _tags: Final[dict[str, Optional[str]]] + _tags: Final[dict[str, str | None]] def __init__(self): self._tags = OrderedDict() @@ -154,7 +154,7 @@ def remove_all(self, keys: TagKeyList): self._tags.pop(key, None) def to_tag_list(self) -> TagList: - tag_list = list() + tag_list = [] for key, value in self._tags.items(): tag_list.append(Tag(key=key, value=value)) return tag_list @@ -173,11 +173,11 @@ def __init__( definition: Definition, role_arn: Arn, logging_config: LoggingConfiguration, - cloud_watch_logging_configuration: Optional[CloudWatchLoggingConfiguration], - create_date: Optional[datetime.datetime] = None, - sm_type: Optional[StateMachineType] = None, - tags: Optional[TagList] = None, - tracing_config: Optional[TracingConfiguration] = None, + cloud_watch_logging_configuration: CloudWatchLoggingConfiguration | None, + create_date: datetime.datetime | None = None, + sm_type: StateMachineType | None = None, + tags: TagList | None = None, + tracing_config: TracingConfiguration | None = None, ): super().__init__( name, @@ -191,7 +191,7 @@ def __init__( tags, tracing_config, ) - self.versions = dict() + self.versions = {} self._version_number = 0 self.tag_manager = TagManager() if tags: @@ -200,10 +200,10 @@ def __init__( def create_revision( self, - definition: Optional[str], - role_arn: Optional[Arn], - logging_configuration: Optional[LoggingConfiguration], - ) -> Optional[RevisionId]: + definition: str | None, + role_arn: Arn | None, + logging_configuration: LoggingConfiguration | None, + ) -> RevisionId | None: update_definition = definition and json.loads(definition) != json.loads(self.definition) if update_definition: self.definition = definition @@ -228,7 +228,7 @@ def create_revision( return self.revision_id - def create_version(self, description: Optional[str]) -> Optional[StateMachineVersion]: + def create_version(self, description: str | None) -> StateMachineVersion | None: if self.revision_id not in self.versions: self._version_number += 1 version = StateMachineVersion( @@ -259,10 +259,10 @@ def itemise(self) -> StateMachineListItem: class StateMachineVersion(StateMachineInstance): source_arn: Arn version: int - description: Optional[str] + description: str | None def __init__( - self, state_machine_revision: StateMachineRevision, version: int, description: Optional[str] + self, state_machine_revision: StateMachineRevision, version: int, description: str | None ): version_arn = f"{state_machine_revision.arn}:{version}" super().__init__( @@ -270,7 +270,7 @@ def __init__( arn=version_arn, definition=state_machine_revision.definition, role_arn=state_machine_revision.role_arn, - create_date=datetime.datetime.now(tz=datetime.timezone.utc), + create_date=datetime.datetime.now(tz=datetime.UTC), sm_type=state_machine_revision.sm_type, logging_config=state_machine_revision.logging_config, cloud_watch_logging_configuration=state_machine_revision.cloud_watch_logging_configuration, diff --git a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py index cc200f09b29c6..92fcb8546c0d6 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py +++ b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution.py @@ -2,7 +2,6 @@ import logging import threading -from typing import Optional from localstack.aws.api.stepfunctions import ( Arn, @@ -37,8 +36,8 @@ class TestStateExecution(Execution): - exec_worker: Optional[TestStateExecutionWorker] - next_state: Optional[str] + exec_worker: TestStateExecutionWorker | None + next_state: str | None class TestCaseExecutionWorkerCommunication(BaseExecutionWorkerCommunication): _execution: TestStateExecution @@ -62,7 +61,7 @@ def __init__( state_machine: StateMachineInstance, start_date: Timestamp, activity_store: dict[Arn, Activity], - input_data: Optional[dict] = None, + input_data: dict | None = None, ): super().__init__( name=name, diff --git a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py index b70c7d41bd6a3..5caae15dc3780 100644 --- a/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py +++ b/localstack-core/localstack/services/stepfunctions/backend/test_state/execution_worker.py @@ -1,5 +1,3 @@ -from typing import Optional - from localstack.services.stepfunctions.asl.component.eval_component import EvalComponent from localstack.services.stepfunctions.asl.eval.environment import Environment from localstack.services.stepfunctions.asl.eval.event.event_manager import ( @@ -18,7 +16,7 @@ class TestStateExecutionWorker(SyncExecutionWorker): - env: Optional[TestStateEnvironment] + env: TestStateEnvironment | None def _get_evaluation_entrypoint(self) -> EvalComponent: return TestStateAmazonStateLanguageParser.parse( diff --git a/localstack-core/localstack/services/stepfunctions/mocking/mock_config.py b/localstack-core/localstack/services/stepfunctions/mocking/mock_config.py index 25f71acee35d5..10213d2b45f56 100644 --- a/localstack-core/localstack/services/stepfunctions/mocking/mock_config.py +++ b/localstack-core/localstack/services/stepfunctions/mocking/mock_config.py @@ -1,5 +1,5 @@ import abc -from typing import Any, Final, Optional +from typing import Any, Final from localstack.services.stepfunctions.mocking.mock_config_file import ( RawMockConfig, @@ -56,7 +56,7 @@ def __init__( ): self.state_name = state_name self.mocked_response_name = mocked_response_name - self.mocked_responses = list() + self.mocked_responses = [] last_range_end: int = -1 mocked_responses_sorted = sorted(mocked_responses, key=lambda mr: mr.range_start) for mocked_response in mocked_responses_sorted: @@ -87,7 +87,7 @@ def __init__( ): self.state_machine_name = state_machine_name self.test_case_name = test_case_name - self.state_mocked_responses = dict() + self.state_mocked_responses = {} for state_mocked_response in state_mocked_responses_list: state_name = state_mocked_response.state_name if state_name in self.state_mocked_responses: @@ -144,14 +144,14 @@ def _mocked_response_from_raw( def _mocked_responses_from_raw( mocked_response_name: str, raw_mock_config: RawMockConfig ) -> list[MockedResponse]: - raw_response_models: Optional[dict[str, RawResponseModel]] = ( - raw_mock_config.MockedResponses.get(mocked_response_name) + raw_response_models: dict[str, RawResponseModel] | None = raw_mock_config.MockedResponses.get( + mocked_response_name ) if not raw_response_models: raise RuntimeError( f"No definitions for mocked response '{mocked_response_name}' in the mock configuration file." ) - mocked_responses: list[MockedResponse] = list() + mocked_responses: list[MockedResponse] = [] for raw_response_model_range, raw_response_model in raw_response_models.items(): mocked_response: MockedResponse = _mocked_response_from_raw( raw_response_model_range=raw_response_model_range, raw_response_model=raw_response_model @@ -187,7 +187,7 @@ def _mock_test_case_from_raw( f"No definitions for test case '{test_case_name}' and " f"state machine '{state_machine_name}' in the mock configuration file." ) - state_mocked_responses_list: list[StateMockedResponses] = list() + state_mocked_responses_list: list[StateMockedResponses] = [] for state_name, mocked_response_name in test_case.root.items(): state_mocked_responses = _state_mocked_responses_from_raw( state_name=state_name, @@ -202,8 +202,8 @@ def _mock_test_case_from_raw( ) -def load_mock_test_case_for(state_machine_name: str, test_case_name: str) -> Optional[MockTestCase]: - raw_mock_config: Optional[RawMockConfig] = _load_sfn_raw_mock_config() +def load_mock_test_case_for(state_machine_name: str, test_case_name: str) -> MockTestCase | None: + raw_mock_config: RawMockConfig | None = _load_sfn_raw_mock_config() if raw_mock_config is None: return None mock_test_case: MockTestCase = _mock_test_case_from_raw( diff --git a/localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py b/localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py index 145ffd20750a2..183116f9e9c6f 100644 --- a/localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py +++ b/localstack-core/localstack/services/stepfunctions/mocking/mock_config_file.py @@ -2,7 +2,7 @@ import os from functools import lru_cache from json import JSONDecodeError -from typing import Any, Dict, Final, Optional +from typing import Any, Final, Optional from pydantic import BaseModel, RootModel, ValidationError, model_validator @@ -44,8 +44,8 @@ class RawResponseModel(BaseModel): model_config = {"frozen": True} - Return: Optional[RawReturnResponse] = None - Throw: Optional[RawThrowResponse] = None + Return: RawReturnResponse | None = None + Throw: RawThrowResponse | None = None @model_validator(mode="before") def validate_response(cls, data: dict) -> dict: @@ -56,7 +56,7 @@ def validate_response(cls, data: dict) -> dict: return data -class RawTestCase(RootModel[Dict[str, str]]): +class RawTestCase(RootModel[dict[str, str]]): """ Represents an individual test case. The keys are state names (e.g., 'LambdaState', 'SQSState') @@ -73,7 +73,7 @@ class RawStateMachine(BaseModel): model_config = {"frozen": True} - TestCases: Dict[str, RawTestCase] + TestCases: dict[str, RawTestCase] class RawMockConfig(BaseModel): @@ -86,8 +86,8 @@ class RawMockConfig(BaseModel): model_config = {"frozen": True} - StateMachines: Dict[str, RawStateMachine] - MockedResponses: Dict[str, Dict[str, RawResponseModel]] + StateMachines: dict[str, RawStateMachine] + MockedResponses: dict[str, dict[str, RawResponseModel]] @lru_cache(maxsize=1) @@ -119,11 +119,11 @@ def _read_sfn_raw_mock_config(file_path: str, modified_epoch: int) -> Optional[R - Logging is used to capture warnings if file access or parsing fails. """ try: - with open(file_path, "r") as df: + with open(file_path) as df: mock_config_str = df.read() mock_config: RawMockConfig = RawMockConfig.model_validate_json(mock_config_str) return mock_config - except (OSError, IOError) as file_error: + except OSError as file_error: LOG.error("Failed to open mock configuration file '%s'. Error: %s", file_path, file_error) return None except ValidationError as validation_error: @@ -168,7 +168,7 @@ def _read_sfn_raw_mock_config(file_path: str, modified_epoch: int) -> Optional[R return None -def _load_sfn_raw_mock_config() -> Optional[RawMockConfig]: +def _load_sfn_raw_mock_config() -> RawMockConfig | None: configuration_file_path = config.SFN_MOCK_CONFIG if not configuration_file_path: return None diff --git a/localstack-core/localstack/services/stepfunctions/packages.py b/localstack-core/localstack/services/stepfunctions/packages.py index b96f7a8d775f0..e9b853682c53b 100644 --- a/localstack-core/localstack/services/stepfunctions/packages.py +++ b/localstack-core/localstack/services/stepfunctions/packages.py @@ -1,6 +1,6 @@ -from localstack.packages import Package, PackageInstaller +from localstack.packages import InstallTarget, Package, PackageInstaller from localstack.packages.core import MavenPackageInstaller -from localstack.packages.java import JavaInstallerMixin +from localstack.packages.java import JavaInstallerMixin, java_package JSONATA_DEFAULT_VERSION = "0.9.7" JACKSON_DEFAULT_VERSION = "2.16.2" @@ -12,9 +12,6 @@ class JSONataPackage(Package): def __init__(self): super().__init__("JSONataLibs", JSONATA_DEFAULT_VERSION) - # Match the dynamodb-local JRE version to reduce the LocalStack image size by sharing the same JRE version - self.java_version = "21" - def get_versions(self) -> list[str]: return list(JSONATA_JACKSON_VERSION_STORE.keys()) @@ -25,6 +22,10 @@ def _get_installer(self, version: str) -> PackageInstaller: class JSONataPackageInstaller(JavaInstallerMixin, MavenPackageInstaller): def __init__(self, version: str): jackson_version = JSONATA_JACKSON_VERSION_STORE[version] + + # Match the dynamodb-local JRE version to reduce the LocalStack image size by sharing the same JRE version + self.java_version = "21" + super().__init__( f"pkg:maven/com.dashjoin/jsonata@{version}", # jackson-databind is imported in jsonata.py as "from com.fasterxml.jackson.databind import ObjectMapper" @@ -35,5 +36,13 @@ def __init__(self, version: str): f"pkg:maven/com.fasterxml.jackson.core/jackson-databind@{jackson_version}", ) + def _prepare_installation(self, target: InstallTarget) -> None: + # override to install correct java version + java_package.get_installer(self.java_version).install(target) + + def get_java_home(self) -> str | None: + """Override to use the specific Java version""" + return java_package.get_installer(self.java_version).get_java_home() + jpype_jsonata_package = JSONataPackage() diff --git a/localstack-core/localstack/services/stepfunctions/provider.py b/localstack-core/localstack/services/stepfunctions/provider.py index 2202014eb0b90..19e3e68e07603 100644 --- a/localstack-core/localstack/services/stepfunctions/provider.py +++ b/localstack-core/localstack/services/stepfunctions/provider.py @@ -4,7 +4,7 @@ import logging import re import time -from typing import Final, Optional +from typing import Final from localstack.aws.api import CommonServiceException, RequestContext from localstack.aws.api.stepfunctions import ( @@ -275,7 +275,7 @@ def _validate_state_machine_alias_name(name: CharacterRestrictedName) -> None: ) def _get_execution(self, context: RequestContext, execution_arn: Arn) -> Execution: - execution: Optional[Execution] = self.get_store(context).executions.get(execution_arn) + execution: Execution | None = self.get_store(context).executions.get(execution_arn) if not execution: raise ExecutionDoesNotExist(f"Execution Does Not Exist: '{execution_arn}'") return execution @@ -283,7 +283,7 @@ def _get_execution(self, context: RequestContext, execution_arn: Arn) -> Executi def _get_executions( self, context: RequestContext, - execution_status: Optional[ExecutionStatus] = None, + execution_status: ExecutionStatus | None = None, ): store = self.get_store(context) execution: list[Execution] = list(store.executions.values()) @@ -297,9 +297,7 @@ def _get_executions( return execution def _get_activity(self, context: RequestContext, activity_arn: Arn) -> Activity: - maybe_activity: Optional[Activity] = self.get_store(context).activities.get( - activity_arn, None - ) + maybe_activity: Activity | None = self.get_store(context).activities.get(activity_arn, None) if maybe_activity is None: raise ActivityDoesNotExist(f"Activity Does Not Exist: '{activity_arn}'") return maybe_activity @@ -312,7 +310,7 @@ def _idempotent_revision( state_machine_type: StateMachineType, logging_configuration: LoggingConfiguration, tracing_configuration: TracingConfiguration, - ) -> Optional[StateMachineRevision]: + ) -> StateMachineRevision | None: # CreateStateMachine's idempotency check is based on the state machine name, definition, type, # LoggingConfiguration and TracingConfiguration. # If a following request has a different roleArn or tags, Step Functions will ignore these differences and @@ -338,11 +336,11 @@ def _idempotent_revision( def _idempotent_start_execution( self, - execution: Optional[Execution], + execution: Execution | None, state_machine: StateMachineInstance, name: Name, input_data: SensitiveData, - ) -> Optional[Execution]: + ) -> Execution | None: # StartExecution is idempotent for STANDARD workflows. For a STANDARD workflow, # if you call StartExecution with the same name and input as a running execution, # the call succeeds and return the same response as the original request. @@ -366,9 +364,7 @@ def _idempotent_start_execution( message=f"Execution Already Exists: '{execution.exec_arn}'", ) - def _revision_by_name( - self, context: RequestContext, name: str - ) -> Optional[StateMachineInstance]: + def _revision_by_name(self, context: RequestContext, name: str) -> StateMachineInstance | None: state_machines: list[StateMachineInstance] = list( self.get_store(context).state_machines.values() ) @@ -445,7 +441,7 @@ def create_state_machine( # CreateStateMachine is an idempotent API. Subsequent requests won't create a duplicate resource if it was # already created. - idem_state_machine: Optional[StateMachineRevision] = self._idempotent_revision( + idem_state_machine: StateMachineRevision | None = self._idempotent_revision( context=context, name=state_machine_name, definition=state_machine_definition, @@ -460,7 +456,7 @@ def create_state_machine( ) # Assert this state machine name is unique. - state_machine_with_name: Optional[StateMachineRevision] = self._revision_by_name( + state_machine_with_name: StateMachineRevision | None = self._revision_by_name( context=context, name=state_machine_name ) if state_machine_with_name is not None: @@ -690,9 +686,7 @@ def describe_state_machine_alias( self, context: RequestContext, state_machine_alias_arn: Arn, **kwargs ) -> DescribeStateMachineAliasOutput: self._validate_state_machine_alias_arn(state_machine_alias_arn=state_machine_alias_arn) - alias: Optional[Alias] = self.get_store(context=context).aliases.get( - state_machine_alias_arn - ) + alias: Alias | None = self.get_store(context=context).aliases.get(state_machine_alias_arn) if alias is None: # TODO: assemble the correct exception raise ValidationException() @@ -778,9 +772,7 @@ def _get_state_machine_arn(state_machine_arn: str) -> str: return state_machine_arn.split("#")[0] @staticmethod - def _get_mock_test_case( - state_machine_arn: str, state_machine_name: str - ) -> Optional[MockTestCase]: + def _get_mock_test_case(state_machine_arn: str, state_machine_name: str) -> MockTestCase | None: """Extract and load a mock test case from a state machine ARN if present.""" parts = state_machine_arn.split("#") if len(parts) != 2: @@ -813,9 +805,9 @@ def start_execution( base_arn = self._get_state_machine_arn(state_machine_arn) store = self.get_store(context=context) - alias: Optional[Alias] = store.aliases.get(base_arn) + alias: Alias | None = store.aliases.get(base_arn) alias_sample_state_machine_version_arn = alias.sample() if alias is not None else None - unsafe_state_machine: Optional[StateMachineInstance] = store.state_machines.get( + unsafe_state_machine: StateMachineInstance | None = store.state_machines.get( alias_sample_state_machine_version_arn or base_arn ) if not unsafe_state_machine: @@ -825,7 +817,7 @@ def start_execution( state_machine_clone = copy.deepcopy(unsafe_state_machine) if input is None: - input_data = dict() + input_data = {} else: try: input_data = json.loads(input) @@ -875,7 +867,7 @@ def start_execution( region_name=context.region, state_machine=state_machine_clone, state_machine_alias_arn=alias.state_machine_alias_arn if alias is not None else None, - start_date=datetime.datetime.now(tz=datetime.timezone.utc), + start_date=datetime.datetime.now(tz=datetime.UTC), cloud_watch_logging_session=cloud_watch_logging_session, input_data=input_data, trace_header=trace_header, @@ -901,7 +893,7 @@ def start_sync_execution( self._validate_state_machine_arn(state_machine_arn) base_arn = self._get_state_machine_arn(state_machine_arn) - unsafe_state_machine: Optional[StateMachineInstance] = self.get_store( + unsafe_state_machine: StateMachineInstance | None = self.get_store( context ).state_machines.get(base_arn) if not unsafe_state_machine: @@ -914,7 +906,7 @@ def start_sync_execution( state_machine_clone = copy.deepcopy(unsafe_state_machine) if input is None: - input_data = dict() + input_data = {} else: try: input_data = json.loads(input) @@ -950,7 +942,7 @@ def start_sync_execution( account_id=context.account_id, region_name=context.region, state_machine=state_machine_clone, - start_date=datetime.datetime.now(tz=datetime.timezone.utc), + start_date=datetime.datetime.now(tz=datetime.UTC), cloud_watch_logging_session=cloud_watch_logging_session, input_data=input_data, trace_header=trace_header, @@ -980,7 +972,7 @@ def describe_execution( @staticmethod def _list_execution_filter( - ex: Execution, state_machine_arn: str, status_filter: Optional[str] + ex: Execution, state_machine_arn: str, status_filter: str | None ) -> bool: state_machine_reference_arn_set = {ex.state_machine_arn, ex.state_machine_version_arn} if state_machine_arn not in state_machine_reference_arn_set: @@ -1105,7 +1097,7 @@ def list_state_machine_aliases( if not isinstance(state_machine_revision, StateMachineRevision): raise InvalidArn(f"Invalid arn: {state_machine_arn}") - state_machine_aliases: StateMachineAliasList = list() + state_machine_aliases: StateMachineAliasList = [] valid_token_found = next_token is None for alias in state_machine_revision.aliases: @@ -1149,7 +1141,7 @@ def list_state_machine_versions( if not isinstance(state_machine_revision, StateMachineRevision): raise InvalidArn(f"Invalid arn: {state_machine_arn}") - state_machine_version_items = list() + state_machine_version_items = [] for version_arn in state_machine_revision.versions.values(): state_machine_version = state_machines[version_arn] if isinstance(state_machine_version, StateMachineVersion): @@ -1245,7 +1237,7 @@ def delete_state_machine_version( if ( state_machine_revision := state_machines.get(state_machine_version.source_arn) ) and isinstance(state_machine_revision, StateMachineRevision): - referencing_alias_names: list[str] = list() + referencing_alias_names: list[str] = [] for alias in state_machine_revision.aliases: if alias.is_router_for(state_machine_version_arn=state_machine_version_arn): referencing_alias_names.append(alias.name) @@ -1275,7 +1267,7 @@ def stop_execution( if execution.sm_type != StateMachineType.STANDARD: self._raise_resource_type_not_in_context(resource_type=execution.sm_type) - stop_date = datetime.datetime.now(tz=datetime.timezone.utc) + stop_date = datetime.datetime.now(tz=datetime.UTC) execution.stop(stop_date=stop_date, cause=cause, error=error) return StopExecutionOutput(stopDate=stop_date) @@ -1328,9 +1320,7 @@ def update_state_machine( target_revision_id = revision_id or state_machine.revision_id version_arn = state_machine.versions[target_revision_id] - update_output = UpdateStateMachineOutput( - updateDate=datetime.datetime.now(tz=datetime.timezone.utc) - ) + update_output = UpdateStateMachineOutput(updateDate=datetime.datetime.now(tz=datetime.UTC)) if revision_id is not None: update_output["revisionId"] = revision_id if version_arn is not None: @@ -1437,7 +1427,7 @@ def describe_map_run( ) -> DescribeMapRunOutput: store = self.get_store(context) for execution in store.executions.values(): - map_run_record: Optional[MapRunRecord] = ( + map_run_record: MapRunRecord | None = ( execution.exec_worker.env.map_run_record_pool_manager.get(map_run_arn) ) if map_run_record is not None: @@ -1477,7 +1467,7 @@ def update_map_run( # TODO: investigate behaviour of empty requests. store = self.get_store(context) for execution in store.executions.values(): - map_run_record: Optional[MapRunRecord] = ( + map_run_record: MapRunRecord | None = ( execution.exec_worker.env.map_run_record_pool_manager.get(map_run_arn) ) if map_run_record is not None: @@ -1507,7 +1497,7 @@ def test_state( definition=definition, static_analysers=[TestStateStaticAnalyser()] ) - name: Optional[Name] = f"TestState-{short_uid()}" + name: Name | None = f"TestState-{short_uid()}" arn = stepfunctions_state_machine_arn( name=name, account_id=context.account_id, region_name=context.region ) @@ -1527,7 +1517,7 @@ def test_state( account_id=context.account_id, region_name=context.region, state_machine=state_machine, - start_date=datetime.datetime.now(tz=datetime.timezone.utc), + start_date=datetime.datetime.now(tz=datetime.UTC), input_data=input_json, activity_store=self.get_store(context).activities, ) @@ -1591,7 +1581,7 @@ def _send_activity_task_started( self, context: RequestContext, task_token: TaskToken, - worker_name: Optional[Name], + worker_name: Name | None, ) -> None: executions: list[Execution] = self._get_executions(context) for execution in executions: @@ -1604,7 +1594,7 @@ def _send_activity_task_started( raise InvalidToken() @staticmethod - def _pull_activity_task(activity: Activity) -> Optional[ActivityTask]: + def _pull_activity_task(activity: Activity) -> ActivityTask | None: seconds_left = 60 while seconds_left > 0: try: @@ -1624,7 +1614,7 @@ def get_activity_task( self._validate_activity_arn(activity_arn) activity = self._get_activity(context=context, activity_arn=activity_arn) - maybe_task: Optional[ActivityTask] = self._pull_activity_task(activity=activity) + maybe_task: ActivityTask | None = self._pull_activity_task(activity=activity) if maybe_task is not None: self._send_activity_task_started( context, maybe_task.task_token, worker_name=worker_name @@ -1645,13 +1635,13 @@ def validate_state_machine_definition( state_machine_type: StateMachineType = request.get("type", StateMachineType.STANDARD) definition: str = request["definition"] - static_analysers = list() + static_analysers = [] if state_machine_type == StateMachineType.STANDARD: static_analysers.append(StaticAnalyser()) else: static_analysers.append(ExpressStaticAnalyser()) - diagnostics: ValidateStateMachineDefinitionDiagnosticList = list() + diagnostics: ValidateStateMachineDefinitionDiagnosticList = [] try: StepFunctionsProvider._validate_definition( definition=definition, static_analysers=static_analysers diff --git a/localstack-core/localstack/services/stepfunctions/quotas.py b/localstack-core/localstack/services/stepfunctions/quotas.py index bf55f8256f51a..e8af8465e086a 100644 --- a/localstack-core/localstack/services/stepfunctions/quotas.py +++ b/localstack-core/localstack/services/stepfunctions/quotas.py @@ -1,12 +1,11 @@ -import json -from typing import Final, Union +from typing import Any, Final from localstack.services.stepfunctions.asl.utils.encoding import to_json_str MAX_STATE_SIZE_UTF8_BYTES: Final[int] = 256 * 1024 # 256 KB of data as a UTF-8 encoded string. -def is_within_size_quota(value: Union[str, json]) -> bool: +def is_within_size_quota(value: str | Any) -> bool: item_str = value if isinstance(value, str) else to_json_str(value) item_bytes = item_str.encode("utf-8") len_item_bytes = len(item_bytes) diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py index bea92e160ec03..766c8c196d6ab 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity.py @@ -2,7 +2,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -14,14 +14,14 @@ class StepFunctionsActivityProperties(TypedDict): - Name: Optional[str] - Arn: Optional[str] - Tags: Optional[list[TagsEntry]] + Name: str | None + Arn: str | None + Tags: list[TagsEntry] | None class TagsEntry(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py index b8f8891464a39..56afaee0a28a1 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_activity_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class StepFunctionsActivityProviderPlugin(CloudFormationResourceProviderPlugin): name = "AWS::StepFunctions::Activity" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.stepfunctions.resource_providers.aws_stepfunctions_activity import ( diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py index a1dd521ab5d4a..449574adfca49 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine.py @@ -4,7 +4,7 @@ import json import re from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import localstack.services.cloudformation.provider_utils as util from localstack.services.cloudformation.resource_provider import ( @@ -18,48 +18,48 @@ class StepFunctionsStateMachineProperties(TypedDict): - RoleArn: Optional[str] - Arn: Optional[str] - Definition: Optional[dict] - DefinitionS3Location: Optional[S3Location] - DefinitionString: Optional[str] - DefinitionSubstitutions: Optional[dict] - LoggingConfiguration: Optional[LoggingConfiguration] - Name: Optional[str] - StateMachineName: Optional[str] - StateMachineRevisionId: Optional[str] - StateMachineType: Optional[str] - Tags: Optional[list[TagsEntry]] - TracingConfiguration: Optional[TracingConfiguration] + RoleArn: str | None + Arn: str | None + Definition: dict | None + DefinitionS3Location: S3Location | None + DefinitionString: str | None + DefinitionSubstitutions: dict | None + LoggingConfiguration: LoggingConfiguration | None + Name: str | None + StateMachineName: str | None + StateMachineRevisionId: str | None + StateMachineType: str | None + Tags: list[TagsEntry] | None + TracingConfiguration: TracingConfiguration | None class CloudWatchLogsLogGroup(TypedDict): - LogGroupArn: Optional[str] + LogGroupArn: str | None class LogDestination(TypedDict): - CloudWatchLogsLogGroup: Optional[CloudWatchLogsLogGroup] + CloudWatchLogsLogGroup: CloudWatchLogsLogGroup | None class LoggingConfiguration(TypedDict): - Destinations: Optional[list[LogDestination]] - IncludeExecutionData: Optional[bool] - Level: Optional[str] + Destinations: list[LogDestination] | None + IncludeExecutionData: bool | None + Level: str | None class TracingConfiguration(TypedDict): - Enabled: Optional[bool] + Enabled: bool | None class S3Location(TypedDict): - Bucket: Optional[str] - Key: Optional[str] - Version: Optional[str] + Bucket: str | None + Key: str | None + Version: str | None class TagsEntry(TypedDict): - Key: Optional[str] - Value: Optional[str] + Key: str | None + Value: str | None REPEATED_INVOCATION = "repeated_invocation" diff --git a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py index 744ff8120e5f6..8683d169a4bf7 100644 --- a/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py +++ b/localstack-core/localstack/services/stepfunctions/resource_providers/aws_stepfunctions_statemachine_plugin.py @@ -1,5 +1,3 @@ -from typing import Optional, Type - from localstack.services.cloudformation.resource_provider import ( CloudFormationResourceProviderPlugin, ResourceProvider, @@ -10,7 +8,7 @@ class StepFunctionsStateMachineProviderPlugin(CloudFormationResourceProviderPlug name = "AWS::StepFunctions::StateMachine" def __init__(self): - self.factory: Optional[Type[ResourceProvider]] = None + self.factory: type[ResourceProvider] | None = None def load(self): from localstack.services.stepfunctions.resource_providers.aws_stepfunctions_statemachine import ( diff --git a/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py b/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py index 95133b4ed47e8..6b1850deb29a2 100644 --- a/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py +++ b/localstack-core/localstack/services/stepfunctions/stepfunctions_utils.py @@ -1,6 +1,5 @@ import base64 import logging -from typing import Dict from localstack.aws.api.stepfunctions import ValidationException from localstack.aws.connect import connect_to @@ -11,7 +10,7 @@ LOG = logging.getLogger(__name__) -def await_sfn_execution_result(execution_arn: str, timeout_secs: int = 60) -> Dict: +def await_sfn_execution_result(execution_arn: str, timeout_secs: int = 60) -> dict: """Wait until the given SFN execution ARN is no longer in RUNNING status, then return execution result.""" arn_data = parse_arn(execution_arn) diff --git a/localstack-core/localstack/services/stores.py b/localstack-core/localstack/services/stores.py index af4d7d1b8b068..2f42e8606dab4 100644 --- a/localstack-core/localstack/services/stores.py +++ b/localstack-core/localstack/services/stores.py @@ -30,9 +30,9 @@ class SqsStore(BaseStore): """ import re -from collections.abc import Callable +from collections.abc import Callable, Iterator from threading import RLock -from typing import Any, Generic, Iterator, Type, TypeVar, Union +from typing import Any, Generic, TypeVar from localstack import config from localstack.utils.aws.aws_stack import get_valid_regions_for_service @@ -52,7 +52,7 @@ class LocalAttribute: Descriptor protocol for marking store attributes as local to a region. """ - def __init__(self, default: Union[Callable, int, float, str, bool, None]): + def __init__(self, default: Callable | int | float | str | bool | None): """ :param default: Default value assigned to the local attribute. Must be a scalar or a callable. @@ -81,7 +81,7 @@ class CrossRegionAttribute: Descriptor protocol for marking store attributes as shared across all regions. """ - def __init__(self, default: Union[Callable, int, float, str, bool, None]): + def __init__(self, default: Callable | int | float | str | bool | None): """ :param default: The default value assigned to the cross-region attribute. This must be a scalar or a callable. @@ -122,7 +122,7 @@ class CrossAccountAttribute: This should be used for resources that are identified by ARNs. """ - def __init__(self, default: Union[Callable, int, float, str, bool, None]): + def __init__(self, default: Callable | int | float | str | bool | None): """ :param default: The default value assigned to the cross-account attribute. This must be a scalar or a callable. @@ -198,7 +198,7 @@ class RegionBundle(dict, Generic[BaseStoreType]): def __init__( self, service_name: str, - store: Type[BaseStoreType], + store: type[BaseStoreType], account_id: str, validate: bool = True, lock: RLock = None, @@ -286,7 +286,7 @@ class AccountRegionBundle(dict, Generic[BaseStoreType]): Encapsulation for all stores for all AWS account IDs. """ - def __init__(self, service_name: str, store: Type[BaseStoreType], validate: bool = True): + def __init__(self, service_name: str, store: type[BaseStoreType], validate: bool = True): """ :param service_name: Name of the service. Must be a valid service defined in botocore. :param store: Class definition of the Store diff --git a/localstack-core/localstack/services/transcribe/packages.py b/localstack-core/localstack/services/transcribe/packages.py index 14faf968c2159..c37bfdb6ab038 100644 --- a/localstack-core/localstack/services/transcribe/packages.py +++ b/localstack-core/localstack/services/transcribe/packages.py @@ -1,5 +1,3 @@ -from typing import List - from localstack.packages import Package from localstack.packages.core import PythonPackageInstaller @@ -13,7 +11,7 @@ def __init__(self, default_version: str = _VOSK_DEFAULT_VERSION): def _get_installer(self, version: str) -> PythonPackageInstaller: return VoskPackageInstaller(version) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [_VOSK_DEFAULT_VERSION] diff --git a/localstack-core/localstack/services/transcribe/provider.py b/localstack-core/localstack/services/transcribe/provider.py index b0d1f62d458ed..9d4bad0f81eaf 100644 --- a/localstack-core/localstack/services/transcribe/provider.py +++ b/localstack-core/localstack/services/transcribe/provider.py @@ -5,7 +5,7 @@ import wave from functools import cache from pathlib import Path -from typing import Any, Tuple +from typing import Any from zipfile import ZipFile from localstack import config @@ -277,7 +277,7 @@ def download_model(name: str) -> str: # Threads # - def _run_transcription_job(self, args: Tuple[TranscribeStore, str]) -> None: + def _run_transcription_job(self, args: tuple[TranscribeStore, str]) -> None: store, job_name = args job = store.transcription_jobs[job_name] diff --git a/localstack-core/localstack/state/inspect.py b/localstack-core/localstack/state/inspect.py index f5b10c6e3e2e4..6bac9d9cc6b74 100644 --- a/localstack-core/localstack/state/inspect.py +++ b/localstack-core/localstack/state/inspect.py @@ -3,7 +3,7 @@ import importlib import logging from functools import singledispatchmethod -from typing import Any, Dict, Optional, TypedDict +from typing import Any, TypedDict from moto.core.base_backend import BackendDict @@ -17,14 +17,14 @@ class ServiceBackend(TypedDict, total=False): """Wrapper of the possible type of backends that a service can use.""" localstack: AccountRegionBundle | None - moto: BackendDict | Dict | None + moto: BackendDict | dict | None class ServiceBackendCollectorVisitor(StateVisitor): """Implementation of StateVisitor meant to collect the backends that a given service use to hold its state.""" store: AccountRegionBundle | None - backend_dict: BackendDict | Dict | None + backend_dict: BackendDict | dict | None def __init__(self) -> None: self.store = None @@ -60,7 +60,7 @@ class ReflectionStateLocator: provider: Any - def __init__(self, provider: Optional[Any] = None, service: Optional[str] = None): + def __init__(self, provider: Any | None = None, service: str | None = None): self.provider = provider self.service = service or provider.service diff --git a/localstack-core/localstack/state/pickle.py b/localstack-core/localstack/state/pickle.py index f458149d49a4c..9124b4b2f0ee4 100644 --- a/localstack-core/localstack/state/pickle.py +++ b/localstack-core/localstack/state/pickle.py @@ -29,7 +29,8 @@ def _recreate(obj_type, obj_queue): """ import inspect -from typing import IO, Any, BinaryIO, Callable, Generic, Type, TypeVar +from collections.abc import Callable +from typing import IO, Any, BinaryIO, Generic, TypeVar import dill import jsonpickle @@ -45,7 +46,7 @@ def _recreate(obj_type, obj_queue): """Type placeholder for pickle._Pickler (which has for instance the save_reduce method)""" -def register(cls: Type = None, subclasses: bool = False): +def register(cls: type = None, subclasses: bool = False): """ Decorator to register a custom type or type tree into the dill pickling dispatcher table. @@ -70,7 +71,7 @@ def _wrapper(fn: Any | Callable[[PythonPickler, Any], None]): return _wrapper -def reducer(cls: Type, restore: Callable = None, subclasses: bool = False): +def reducer(cls: type, restore: Callable = None, subclasses: bool = False): """ Convenience decorator to simplify the following pattern:: @@ -118,14 +119,14 @@ def _reducer(pickler, obj): def add_dispatch_entry( - cls: Type, fn: Callable[[PythonPickler, Any], None], subclasses: bool = False + cls: type, fn: Callable[[PythonPickler, Any], None], subclasses: bool = False ): Pickler.dispatch_overwrite[cls] = fn if subclasses: Pickler.match_subclasses_of.add(cls) -def remove_dispatch_entry(cls: Type): +def remove_dispatch_entry(cls: type): try: del Pickler.dispatch_overwrite[cls] except KeyError: @@ -190,7 +191,7 @@ class _SuperclassMatchingTypeDict(MetaCatchingDict): """ - def __init__(self, seq=None, match_subclasses_of: set[Type] = None): + def __init__(self, seq=None, match_subclasses_of: set[type] = None): if seq is not None: super().__init__(seq) else: @@ -215,8 +216,8 @@ class Pickler(dill.Pickler): Custom dill pickler that considers dispatchers and subclass dispatchers registered via ``register``. """ - match_subclasses_of: set[Type] = set() - dispatch_overwrite: dict[Type, Callable] = {} + match_subclasses_of: set[type] = set() + dispatch_overwrite: dict[type, Callable] = {} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -234,9 +235,9 @@ class PickleEncoder(Encoder): extended with custom serializers. """ - pickler_class: Type[dill.Pickler] + pickler_class: type[dill.Pickler] - def __init__(self, pickler_class: Type[dill.Pickler] = None): + def __init__(self, pickler_class: type[dill.Pickler] = None): self.pickler_class = pickler_class or Pickler def encode(self, obj: Any, file: BinaryIO): @@ -249,9 +250,9 @@ class PickleDecoder(Decoder): extended with custom serializers. """ - unpickler_class: Type[dill.Unpickler] + unpickler_class: type[dill.Unpickler] - def __init__(self, unpickler_class: Type[dill.Unpickler] = None): + def __init__(self, unpickler_class: type[dill.Unpickler] = None): self.unpickler_class = unpickler_class or dill.Unpickler def decode(self, file: BinaryIO) -> Any: @@ -263,7 +264,7 @@ class JsonEncoder(Encoder): An Encoder that uses ``jsonpickle`` under the hood. """ - def __init__(self, pickler_class: Type[jsonpickle.Pickler] = None): + def __init__(self, pickler_class: type[jsonpickle.Pickler] = None): self.pickler_class = pickler_class or jsonpickle.Pickler() def encode(self, obj: Any, file: IO[bytes]): @@ -276,9 +277,9 @@ class JsonDecoder(Decoder): A Decoder that uses ``jsonpickle`` under the hood. """ - unpickler_class: Type[jsonpickle.Unpickler] + unpickler_class: type[jsonpickle.Unpickler] - def __init__(self, unpickler_class: Type[jsonpickle.Unpickler] = None): + def __init__(self, unpickler_class: type[jsonpickle.Unpickler] = None): self.unpickler_class = unpickler_class or jsonpickle.Unpickler() def decode(self, file: IO[bytes]) -> Any: diff --git a/localstack-core/localstack/testing/aws/asf_utils.py b/localstack-core/localstack/testing/aws/asf_utils.py index 33035496ebf2f..e48435677c7b8 100644 --- a/localstack-core/localstack/testing/aws/asf_utils.py +++ b/localstack-core/localstack/testing/aws/asf_utils.py @@ -3,12 +3,13 @@ import inspect import pkgutil import re +from re import Pattern from types import FunctionType, ModuleType, NoneType, UnionType -from typing import Optional, Pattern, Union, get_args, get_origin +from typing import Union, get_args, get_origin def _import_submodules( - package_name: str, module_regex: Optional[Pattern] = None, recursive: bool = True + package_name: str, module_regex: Pattern | None = None, recursive: bool = True ) -> dict[str, ModuleType]: """ Imports all submodules of the given package with the defined (optional) module_suffix. diff --git a/localstack-core/localstack/testing/aws/lambda_utils.py b/localstack-core/localstack/testing/aws/lambda_utils.py index 764605f46962a..929ba0fed1fa5 100644 --- a/localstack-core/localstack/testing/aws/lambda_utils.py +++ b/localstack-core/localstack/testing/aws/lambda_utils.py @@ -4,8 +4,9 @@ import os import subprocess import zipfile +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import TYPE_CHECKING, Literal, Mapping, Optional, Sequence, overload +from typing import TYPE_CHECKING, Literal, Optional, overload from localstack import config from localstack.services.lambda_.runtimes import RUNTIMES_AGGREGATED @@ -176,27 +177,27 @@ def __init__( def create_function( self, *, - FunctionName: Optional[str] = None, - Role: Optional[str] = None, + FunctionName: str | None = None, + Role: str | None = None, Code: Optional["FunctionCodeTypeDef"] = None, Runtime: Optional["RuntimeType"] = None, - Handler: Optional[str] = None, - Description: Optional[str] = None, - Timeout: Optional[int] = None, - MemorySize: Optional[int] = None, - Publish: Optional[bool] = None, + Handler: str | None = None, + Description: str | None = None, + Timeout: int | None = None, + MemorySize: int | None = None, + Publish: bool | None = None, VpcConfig: Optional["VpcConfigTypeDef"] = None, PackageType: Optional["PackageTypeType"] = None, DeadLetterConfig: Optional["DeadLetterConfigTypeDef"] = None, Environment: Optional["EnvironmentTypeDef"] = None, - KMSKeyArn: Optional[str] = None, + KMSKeyArn: str | None = None, TracingConfig: Optional["TracingConfigTypeDef"] = None, - Tags: Optional[Mapping[str, str]] = None, - Layers: Optional[Sequence[str]] = None, - FileSystemConfigs: Optional[Sequence["FileSystemConfigTypeDef"]] = None, + Tags: Mapping[str, str] | None = None, + Layers: Sequence[str] | None = None, + FileSystemConfigs: Sequence["FileSystemConfigTypeDef"] | None = None, ImageConfig: Optional["ImageConfigTypeDef"] = None, - CodeSigningConfigArn: Optional[str] = None, - Architectures: Optional[Sequence["ArchitectureType"]] = None, + CodeSigningConfigArn: str | None = None, + Architectures: Sequence["ArchitectureType"] | None = None, EphemeralStorage: Optional["EphemeralStorageTypeDef"] = None, ) -> "FunctionConfigurationResponseMetadataTypeDef": ... diff --git a/localstack-core/localstack/testing/aws/util.py b/localstack-core/localstack/testing/aws/util.py index 2fadd02b9b257..f0f00a31a5045 100644 --- a/localstack-core/localstack/testing/aws/util.py +++ b/localstack-core/localstack/testing/aws/util.py @@ -1,6 +1,7 @@ import functools import os -from typing import Callable, Dict, TypeVar +from collections.abc import Callable +from typing import TypeVar import boto3 import botocore @@ -80,7 +81,7 @@ def is_user_ready(): def create_client_with_keys( service: str, - keys: Dict[str, str], + keys: dict[str, str], region_name: str, client_config: Config = None, ): diff --git a/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py b/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py index 745a547f078c3..137766d430f5b 100644 --- a/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py +++ b/localstack-core/localstack/testing/pytest/cloudformation/fixtures.py @@ -1,8 +1,10 @@ import json from collections import defaultdict +from collections.abc import Generator from typing import Callable, Optional, TypedDict import pytest +from botocore.exceptions import WaiterError from localstack.aws.api.cloudformation import DescribeChangeSetOutput, StackEvent from localstack.aws.connect import ServiceLevelClientFactory @@ -32,23 +34,41 @@ def normalize_event(event: StackEvent) -> NormalizedEvent: @pytest.fixture -def capture_per_resource_events( - aws_client: ServiceLevelClientFactory, -) -> Callable[[str], PerResourceStackEvents]: - def capture(stack_name: str) -> dict: +def capture_resource_state_changes(aws_client: ServiceLevelClientFactory): + def capture(stack_name: str) -> Generator[StackEvent, None, None]: + resource_states: dict[str, str] = {} events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ "StackEvents" ] - per_resource_events = defaultdict(list) for event in events: # TODO: not supported events if event.get("ResourceStatus") in { "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", - "DELETE_IN_PROGRESS", - "DELETE_COMPLETE", }: continue + resource = event["LogicalResourceId"] + status = event["ResourceStatus"] + if resource not in resource_states: + yield event + resource_states[resource] = status + continue + + if status != resource_states[resource]: + yield event + resource_states[resource] = status + + return capture + + +@pytest.fixture +def capture_per_resource_events( + capture_resource_state_changes, +) -> Callable[[str], PerResourceStackEvents]: + def capture(stack_name: str) -> dict: + per_resource_events = defaultdict(list) + events = capture_resource_state_changes(stack_name) + for event in events: if logical_resource_id := event.get("LogicalResourceId"): resource_name = ( logical_resource_id @@ -91,8 +111,8 @@ def capture(stack_name: str) -> dict: def _normalise_describe_change_set_output(value: DescribeChangeSetOutput) -> None: - value.get("Changes", list()).sort( - key=lambda change: change.get("ResourceChange", dict()).get("LogicalResourceId", str()) + value.get("Changes", []).sort( + key=lambda change: change.get("ResourceChange", {}).get("LogicalResourceId", "") ) @@ -107,8 +127,16 @@ def capture_update_process(aws_client_no_retry, cleanups, capture_per_resource_e change_set_name = f"cs-{short_uid()}" def inner( - snapshot, t1: dict | str, t2: dict | str, p1: dict | None = None, p2: dict | None = None - ): + snapshot, + t1: dict | str, + t2: dict | str, + p1: dict | None = None, + p2: dict | None = None, + custom_update_step: Callable[[], None] | None = None, + ) -> str: + """ + :return: stack id + """ snapshot.add_transformer(snapshot.transform.cloudformation_api()) if isinstance(t1, dict): @@ -131,6 +159,11 @@ def inner( ChangeSetName=change_set_name, TemplateBody=t1, ChangeSetType="CREATE", + Capabilities=[ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND", + ], Parameters=[{"ParameterKey": k, "ParameterValue": v} for (k, v) in p1.items()], ) snapshot.match("create-change-set-1", change_set_details) @@ -142,7 +175,7 @@ def inner( cleanups.append( lambda: call_safe( aws_client_no_retry.cloudformation.delete_change_set, - kwargs=dict(ChangeSetName=change_set_id), + kwargs={"ChangeSetName": change_set_id}, ) ) @@ -173,7 +206,7 @@ def inner( # ensure stack deletion cleanups.append( lambda: call_safe( - aws_client_no_retry.cloudformation.delete_stack, kwargs=dict(StackName=stack_id) + aws_client_no_retry.cloudformation.delete_stack, kwargs={"StackName": stack_id} ) ) @@ -182,6 +215,10 @@ def inner( ] snapshot.match("post-create-1-describe", describe) + # run any custom steps if present + if custom_update_step: + custom_update_step() + # update stack change_set_details = aws_client_no_retry.cloudformation.create_change_set( StackName=stack_name, @@ -189,13 +226,24 @@ def inner( TemplateBody=t2, ChangeSetType="UPDATE", Parameters=[{"ParameterKey": k, "ParameterValue": v} for (k, v) in p2.items()], + Capabilities=[ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND", + ], ) snapshot.match("create-change-set-2", change_set_details) stack_id = change_set_details["StackId"] change_set_id = change_set_details["Id"] - aws_client_no_retry.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=change_set_id - ) + try: + aws_client_no_retry.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_id + ) + except WaiterError as e: + desc = aws_client_no_retry.cloudformation.describe_change_set( + ChangeSetName=change_set_id + ) + raise RuntimeError(f"Change set deployment failed: {desc}") from e describe_change_set_with_prop_values = ( aws_client_no_retry.cloudformation.describe_change_set( @@ -239,4 +287,6 @@ def inner( events = capture_per_resource_events(stack_id) snapshot.match("per-resource-events", events) + return stack_id + yield inner diff --git a/localstack-core/localstack/testing/pytest/container.py b/localstack-core/localstack/testing/pytest/container.py index fd904f6a86233..aebb452c5d3d1 100644 --- a/localstack-core/localstack/testing/pytest/container.py +++ b/localstack-core/localstack/testing/pytest/container.py @@ -2,7 +2,8 @@ import os import shlex import threading -from typing import Callable, Generator, List, Optional +from collections.abc import Generator +from typing import Callable, Optional import pytest @@ -31,14 +32,14 @@ class ContainerFactory: def __init__(self): - self._containers: List[Container] = [] + self._containers: list[Container] = [] def __call__( self, # convenience properties pro: bool = False, - publish: Optional[List[int]] = None, - configurators: Optional[List[ContainerConfigurator]] = None, + publish: Optional[list[int]] = None, + configurators: Optional[list[ContainerConfigurator]] = None, # ContainerConfig properties **kwargs, ) -> Container: diff --git a/localstack-core/localstack/testing/pytest/filters.py b/localstack-core/localstack/testing/pytest/filters.py index 2e7f0a8d0a780..8f32827364950 100644 --- a/localstack-core/localstack/testing/pytest/filters.py +++ b/localstack-core/localstack/testing/pytest/filters.py @@ -1,5 +1,3 @@ -from typing import List - import pytest from _pytest.config import Config, PytestPluginManager from _pytest.config.argparsing import Parser @@ -13,7 +11,7 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager): @pytest.hookimpl -def pytest_collection_modifyitems(session: Session, config: Config, items: List[Item]): +def pytest_collection_modifyitems(session: Session, config: Config, items: list[Item]): filter_fixtures_option = config.getoption("--filter-fixtures") if filter_fixtures_option: # TODO: add more sophisticated combinations (=> like pytest -m and -k) diff --git a/localstack-core/localstack/testing/pytest/fixtures.py b/localstack-core/localstack/testing/pytest/fixtures.py index f4433dc57899f..8b14aa656cfa5 100644 --- a/localstack-core/localstack/testing/pytest/fixtures.py +++ b/localstack-core/localstack/testing/pytest/fixtures.py @@ -6,7 +6,7 @@ import re import textwrap import time -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple +from typing import TYPE_CHECKING, Any, Callable, Optional, Unpack import botocore.auth import botocore.config @@ -21,7 +21,8 @@ from werkzeug import Request, Response from localstack import config -from localstack.aws.api.ec2 import CreateSecurityGroupRequest +from localstack.aws.api.cloudformation import CreateChangeSetInput, Parameter +from localstack.aws.api.ec2 import CreateSecurityGroupRequest, CreateVpcEndpointRequest, VpcEndpoint from localstack.aws.connect import ServiceLevelClientFactory from localstack.services.stores import ( AccountRegionBundle, @@ -593,7 +594,7 @@ def _allow_sns_topic(sqs_queue_url, sqs_queue_arn, sns_topic_arn) -> None: def sns_create_sqs_subscription(sns_allow_topic_sqs_queue, sqs_get_queue_arn, aws_client): subscriptions = [] - def _factory(topic_arn: str, queue_url: str, **kwargs) -> Dict[str, str]: + def _factory(topic_arn: str, queue_url: str, **kwargs) -> dict[str, str]: queue_arn = sqs_get_queue_arn(queue_url) # connect sns topic to sqs @@ -630,7 +631,7 @@ def sns_create_http_endpoint(sns_create_topic, sns_subscription, aws_client): def _create_http_endpoint( raw_message_delivery: bool = False, - ) -> Tuple[str, str, str, HTTPServer]: + ) -> tuple[str, str, str, HTTPServer]: server = HTTPServer() server.start() http_servers.append(server) @@ -1004,7 +1005,7 @@ def opensearch_document_path(opensearch_endpoint, aws_client): # Cleanup fixtures @pytest.fixture def cleanup_stacks(aws_client): - def _cleanup_stacks(stacks: List[str]) -> None: + def _cleanup_stacks(stacks: list[str]) -> None: stacks = ensure_list(stacks) for stack in stacks: try: @@ -1018,7 +1019,7 @@ def _cleanup_stacks(stacks: List[str]) -> None: @pytest.fixture def cleanup_changesets(aws_client): - def _cleanup_changesets(changesets: List[str]) -> None: + def _cleanup_changesets(changesets: list[str]) -> None: changesets = ensure_list(changesets) for cs in changesets: try: @@ -1039,7 +1040,7 @@ class DeployResult: stack_id: str stack_name: str change_set_name: str - outputs: Dict[str, str] + outputs: dict[str, str] destroy: Callable[[], None] @@ -1089,12 +1090,13 @@ def _deploy( change_set_name: Optional[str] = None, template: Optional[str] = None, template_path: Optional[str | os.PathLike] = None, - template_mapping: Optional[Dict[str, Any]] = None, - parameters: Optional[Dict[str, str]] = None, + template_mapping: Optional[dict[str, Any]] = None, + parameters: Optional[dict[str, str]] = None, role_arn: Optional[str] = None, max_wait: Optional[int] = None, delay_between_polls: Optional[int] = 2, custom_aws_client: Optional[ServiceLevelClientFactory] = None, + raw_parameters: Optional[list[Parameter]] = None, ) -> DeployResult: if is_update: assert stack_name @@ -1110,20 +1112,21 @@ def _deploy( raise RuntimeError(f"Could not find file {os.path.realpath(template_path)}") template_rendered = render_template(template, **(template_mapping or {})) - kwargs = dict( + kwargs = CreateChangeSetInput( StackName=stack_name, ChangeSetName=change_set_name, TemplateBody=template_rendered, Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], ChangeSetType=("UPDATE" if is_update else "CREATE"), - Parameters=[ - { - "ParameterKey": k, - "ParameterValue": v, - } - for (k, v) in (parameters or {}).items() - ], ) + kwargs["Parameters"] = [] + if parameters: + kwargs["Parameters"] = [ + Parameter(ParameterKey=k, ParameterValue=v) for (k, v) in parameters.items() + ] + elif raw_parameters: + kwargs["Parameters"] = raw_parameters + if role_arn is not None: kwargs["RoleARN"] = role_arn @@ -1238,7 +1241,7 @@ def is_stack_deleted(aws_client): return _has_stack_status(aws_client.cloudformation, ["DELETE_COMPLETE"]) -def _has_stack_status(cfn_client, statuses: List[str]): +def _has_stack_status(cfn_client, statuses: list[str]): def _has_status(stack_id: str): def _inner(): resp = cfn_client.describe_stacks(StackName=stack_id) @@ -1514,7 +1517,7 @@ def _create_event_source_mapping(*args, **kwargs): @pytest.fixture def check_lambda_logs(aws_client): - def _check_logs(func_name: str, expected_lines: List[str] = None) -> List[str]: + def _check_logs(func_name: str, expected_lines: list[str] = None) -> list[str]: if not expected_lines: expected_lines = [] log_events = get_lambda_logs(func_name, logs_client=aws_client.logs) @@ -2046,6 +2049,45 @@ def factory(ports=None, ip_protocol: str = "tcp", **kwargs): LOG.debug("Error cleaning up EC2 security group: %s, %s", sg_group_id, e) +@pytest.fixture +def ec2_create_vpc_endpoint(aws_client): + vpc_endpoints = [] + + def _create(**kwargs: Unpack[CreateVpcEndpointRequest]) -> VpcEndpoint: + endpoint = aws_client.ec2.create_vpc_endpoint(**kwargs) + endpoint_id = endpoint["VpcEndpoint"]["VpcEndpointId"] + vpc_endpoints.append(endpoint_id) + + def _check_available() -> VpcEndpoint: + result = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id]) + _endpoint_details = result["VpcEndpoints"][0] + assert _endpoint_details["State"] == "available" + + return _endpoint_details + + return retry(_check_available, retries=30, sleep=5 if is_aws_cloud() else 1) + + yield _create + + try: + aws_client.ec2.delete_vpc_endpoints(VpcEndpointIds=vpc_endpoints) + except Exception as e: + LOG.error("Error cleaning up VPC endpoint: %s, %s", vpc_endpoints, e) + + def wait_for_endpoint_deleted(): + try: + endpoints = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=vpc_endpoints) + assert len(endpoints["VpcEndpoints"]) == 0 or all( + endpoint["State"] == "Deleted" for endpoint in endpoints["VpcEndpoints"] + ) + except botocore.exceptions.ClientError: + pass + + # the vpc can't be deleted if an endpoint exists + if is_aws_cloud(): + retry(wait_for_endpoint_deleted, retries=30, sleep=10 if is_aws_cloud() else 1) + + @pytest.fixture def cleanups(): cleanup_fns = [] @@ -2251,7 +2293,7 @@ def echo_http_server_post(echo_http_server): return f"{echo_http_server}post" -def create_policy_doc(effect: str, actions: List, resource=None) -> Dict: +def create_policy_doc(effect: str, actions: list, resource=None) -> dict: actions = ensure_list(actions) resource = resource or "*" return { @@ -2540,14 +2582,22 @@ def _create_rule(**kwargs): @pytest.fixture def sqs_as_events_target(aws_client, sqs_get_queue_arn): + """ + Fixture that creates an SQS queue and sets it up as a target for EventBridge events. + """ queue_urls = [] - def _sqs_as_events_target(queue_name: str | None = None) -> tuple[str, str]: + def _sqs_as_events_target( + queue_name: str | None = None, custom_aws_client=None + ) -> tuple[str, str]: if not queue_name: queue_name = f"tests-queue-{short_uid()}" - sqs_client = aws_client.sqs + if custom_aws_client: + sqs_client = custom_aws_client.sqs + else: + sqs_client = aws_client.sqs queue_url = sqs_client.create_queue(QueueName=queue_name)["QueueUrl"] - queue_urls.append(queue_url) + queue_urls.append((queue_url, sqs_client)) queue_arn = sqs_get_queue_arn(queue_url) policy = { "Version": "2012-10-17", @@ -2565,17 +2615,98 @@ def _sqs_as_events_target(queue_name: str | None = None) -> tuple[str, str]: sqs_client.set_queue_attributes( QueueUrl=queue_url, Attributes={"Policy": json.dumps(policy)} ) - return queue_url, queue_arn + return queue_url, queue_arn, queue_name yield _sqs_as_events_target - for queue_url in queue_urls: + for queue_url, sqs_client in queue_urls: try: - aws_client.sqs.delete_queue(QueueUrl=queue_url) + sqs_client.delete_queue(QueueUrl=queue_url) except Exception as e: LOG.debug("error cleaning up queue %s: %s", queue_url, e) +@pytest.fixture +def create_role_event_bus_source_to_bus_target(create_iam_role_with_policy): + def _create_role_event_bus_to_bus(): + assume_role_policy_document_bus_source_to_bus_target = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "events.amazonaws.com"}, + "Action": "sts:AssumeRole", + } + ], + } + + policy_document_bus_source_to_bus_target = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "", + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": "arn:aws:events:*:*:event-bus/*", + } + ], + } + + role_arn_bus_source_to_bus_target = create_iam_role_with_policy( + RoleDefinition=assume_role_policy_document_bus_source_to_bus_target, + PolicyDefinition=policy_document_bus_source_to_bus_target, + ) + + return role_arn_bus_source_to_bus_target + + yield _create_role_event_bus_to_bus + + +@pytest.fixture +def get_primary_secondary_client( + aws_client_factory, + secondary_aws_client_factory, + region_name, + secondary_region_name, + account_id, + secondary_account_id, +): + def _get_primary_secondary_clients(cross_scenario: str): + """ + Returns primary and secondary AWS clients based on the cross-scenario. + :param cross_scenario: The scenario for cross-region or cross-account testing. + Options: "region", "account", "region_account" + account_region cross scenario is not supported by AWS + :return: A dictionary containing primary and secondary AWS clients, and their respective region and account IDs. + """ + secondary_region = secondary_region_name + secondary_account = secondary_account_id + if cross_scenario not in ["region", "account", "region_account"]: + raise ValueError(f"cross_scenario {cross_scenario} not supported") + + primary_client = aws_client_factory(region_name=region_name) + + if cross_scenario == "region": + secondary_account = account_id + secondary_client = aws_client_factory(region_name=secondary_region_name) + + elif cross_scenario == "account": + secondary_region = region_name + secondary_client = secondary_aws_client_factory(region_name=region_name) + + elif cross_scenario == "region_account": + secondary_client = secondary_aws_client_factory(region_name=secondary_region) + + return { + "primary_aws_client": primary_client, + "secondary_aws_client": secondary_client, + "secondary_region_name": secondary_region, + "secondary_account_id": secondary_account, + } + + return _get_primary_secondary_clients + + @pytest.fixture def clean_up( aws_client, @@ -2598,10 +2729,10 @@ def _clean_up( if rule_name: call_safe(events_client.delete_rule, kwargs=dict(Name=rule_name, Force=True, **kwargs)) if bus_name: - call_safe(events_client.delete_event_bus, kwargs=dict(Name=bus_name)) + call_safe(events_client.delete_event_bus, kwargs={"Name": bus_name}) if queue_url: sqs_client = aws_client.sqs - call_safe(sqs_client.delete_queue, kwargs=dict(QueueUrl=queue_url)) + call_safe(sqs_client.delete_queue, kwargs={"QueueUrl": queue_url}) if log_group_name: logs_client = aws_client.logs diff --git a/localstack-core/localstack/testing/pytest/in_memory_localstack.py b/localstack-core/localstack/testing/pytest/in_memory_localstack.py index d31a570ac4b30..f9ad069bbc901 100644 --- a/localstack-core/localstack/testing/pytest/in_memory_localstack.py +++ b/localstack-core/localstack/testing/pytest/in_memory_localstack.py @@ -71,6 +71,8 @@ def pytest_runtestloop(session: Session): # configure os.environ[ENV_INTERNAL_TEST_RUN] = "1" + localstack_config.INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE = True + safe_requests.verify_ssl = False from localstack.runtime import current diff --git a/localstack-core/localstack/testing/pytest/marking.py b/localstack-core/localstack/testing/pytest/marking.py index 5afcca6cdc24f..333993a89ab7d 100644 --- a/localstack-core/localstack/testing/pytest/marking.py +++ b/localstack-core/localstack/testing/pytest/marking.py @@ -3,7 +3,7 @@ """ import os -from typing import TYPE_CHECKING, Callable, List, Optional +from typing import TYPE_CHECKING, Callable, Optional import pytest from _pytest.config import PytestPluginManager @@ -36,13 +36,13 @@ class SkipSnapshotVerifyMarker: def __call__( self, *, - paths: "Optional[List[str]]" = None, + paths: "Optional[list[str]]" = None, condition: "Optional[Callable[[...], bool]]" = None, ): ... class MultiRuntimeMarker: - def __call__(self, *, scenario: str, runtimes: Optional[List[str]] = None): ... + def __call__(self, *, scenario: str, runtimes: Optional[list[str]] = None): ... class SnapshotMarkers: @@ -82,7 +82,7 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager): ) -def enforce_single_aws_marker(items: List[pytest.Item]): +def enforce_single_aws_marker(items: list[pytest.Item]): """Enforce that each test has exactly one aws compatibility marker""" marker_errors = [] @@ -91,7 +91,7 @@ def enforce_single_aws_marker(items: List[pytest.Item]): if "tests/aws" not in item.fspath.dirname: continue - aws_markers = list() + aws_markers = [] for mark in item.iter_markers(): if mark.name.startswith("aws_"): aws_markers.append(mark.name) @@ -107,7 +107,7 @@ def enforce_single_aws_marker(items: List[pytest.Item]): raise pytest.UsageError(*marker_errors) -def filter_by_markers(config: "Config", items: List[pytest.Item]): +def filter_by_markers(config: "Config", items: list[pytest.Item]): """Filter tests by markers.""" from localstack import config as localstack_config from localstack.utils.bootstrap import in_ci @@ -154,7 +154,7 @@ def filter_by_markers(config: "Config", items: List[pytest.Item]): @pytest.hookimpl def pytest_collection_modifyitems( - session: pytest.Session, config: "Config", items: List[pytest.Item] + session: pytest.Session, config: "Config", items: list[pytest.Item] ) -> None: enforce_single_aws_marker(items) filter_by_markers(config, items) diff --git a/localstack-core/localstack/testing/pytest/path_filter.py b/localstack-core/localstack/testing/pytest/path_filter.py index d3e13c0016143..13d562abd6a4f 100644 --- a/localstack-core/localstack/testing/pytest/path_filter.py +++ b/localstack-core/localstack/testing/pytest/path_filter.py @@ -49,7 +49,7 @@ def pytest_collection_modifyitems(config, items): if not os.path.exists(pathfilter_file): raise ValueError(f"Pathfilter file does not exist: {pathfilter_file}") - with open(pathfilter_file, "r") as f: + with open(pathfilter_file) as f: pathfilter_substrings = [line.strip() for line in f.readlines() if line.strip()] if not pathfilter_substrings: diff --git a/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py b/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py index 13a134d269e85..bdd0a99ed2027 100644 --- a/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py +++ b/localstack-core/localstack/testing/pytest/stepfunctions/fixtures.py @@ -271,7 +271,7 @@ def _wait_sfn_can_assume_role(): @pytest.fixture def create_state_machine(): - created_state_machine_references = list() + created_state_machine_references = [] def _create_state_machine(target_aws_client, **kwargs): sfn_client = target_aws_client.stepfunctions @@ -299,7 +299,7 @@ def _create_state_machine(target_aws_client, **kwargs): @pytest.fixture def create_state_machine_alias(): - state_machine_alias_arn_and_client = list() + state_machine_alias_arn_and_client = [] def _create_state_machine_alias(target_aws_client, **kwargs): step_functions_client = target_aws_client.stepfunctions @@ -324,7 +324,7 @@ def _create_state_machine_alias(target_aws_client, **kwargs): @pytest.fixture def create_activity(aws_client): - activities_arns: Final[list[str]] = list() + activities_arns: Final[list[str]] = [] def _create_activity(**kwargs): create_output = aws_client.stepfunctions.create_activity(**kwargs) @@ -778,7 +778,7 @@ def _create() -> str: @pytest.fixture def create_cross_account_admin_role_and_policy(create_state_machine, create_state_machine_iam_role): - created = list() + created = [] def _create_role_and_policy(trusting_aws_client, trusted_aws_client, trusted_account_id) -> str: trusting_iam_client = trusting_aws_client.iam diff --git a/localstack-core/localstack/testing/pytest/stepfunctions/utils.py b/localstack-core/localstack/testing/pytest/stepfunctions/utils.py index 401b6173d66f4..19f954e51fb54 100644 --- a/localstack-core/localstack/testing/pytest/stepfunctions/utils.py +++ b/localstack-core/localstack/testing/pytest/stepfunctions/utils.py @@ -189,7 +189,7 @@ def await_state_machine_version_listed( def await_on_execution_events( stepfunctions_client, execution_arn: str, check_func: Callable[[HistoryEventList], bool] ) -> HistoryEventList: - events: HistoryEventList = list() + events: HistoryEventList = [] def _run_check(): nonlocal events @@ -342,7 +342,7 @@ def _validation_function(log_events: list) -> bool: def _await_on_execution_log_stream_created(target_aws_client, log_group_name: str) -> str: logs_client = target_aws_client.logs - log_stream_name = str() + log_stream_name = "" def _run_check(): nonlocal log_stream_name @@ -376,7 +376,7 @@ def await_on_execution_logs( log_stream_name = _await_on_execution_log_stream_created(target_aws_client, log_group_name) logs_client = target_aws_client.logs - events: HistoryEventList = list() + events: HistoryEventList = [] def _run_check(): nonlocal events @@ -889,7 +889,7 @@ def create_and_record_events( stepfunctions_client=target_aws_client.stepfunctions, execution_arn=execution_arn ) - stepfunctions_events = list() + stepfunctions_events = [] def _get_events(): received = target_aws_client.sqs.receive_message(QueueUrl=queue_url) @@ -905,7 +905,7 @@ def _get_events(): def record_sqs_events(target_aws_client, queue_url, sfn_snapshot, num_events): - stepfunctions_events = list() + stepfunctions_events = [] def _get_events(): received = target_aws_client.sqs.receive_message(QueueUrl=queue_url) @@ -916,7 +916,7 @@ def _get_events(): return len(stepfunctions_events) == num_events poll_condition(_get_events, timeout=60) - stepfunctions_events.sort(key=lambda e: json.dumps(e.get("detail", dict()))) + stepfunctions_events.sort(key=lambda e: json.dumps(e.get("detail", {}))) sfn_snapshot.match("stepfunctions_events", stepfunctions_events) @@ -931,7 +931,7 @@ def __init__(self, events_jsonpath: str = "$..events"): @staticmethod def _normalise_events(events: list[dict]) -> None: start_idx = None - sublist = list() + sublist = [] in_sublist = False for i, event in enumerate(events): event_type = event.get("type") diff --git a/localstack-core/localstack/testing/pytest/validation_tracking.py b/localstack-core/localstack/testing/pytest/validation_tracking.py index cb3fd9eb48dae..9c3531a31781a 100644 --- a/localstack-core/localstack/testing/pytest/validation_tracking.py +++ b/localstack-core/localstack/testing/pytest/validation_tracking.py @@ -9,7 +9,7 @@ import json import os from pathlib import Path -from typing import Dict, Optional +from typing import Optional import pytest from pluggy import Result @@ -17,7 +17,7 @@ from localstack.testing.aws.util import is_aws_cloud -durations_key = StashKey[Dict[str, float]]() +durations_key = StashKey[dict[str, float]]() """ Stores phase durations on the test node between execution phases. See https://docs.pytest.org/en/latest/reference/reference.html#pytest.Stash @@ -35,7 +35,7 @@ def find_validation_data_for_item(item: pytest.Item) -> Optional[dict]: if not os.path.exists(snapshot_path): return None - with open(snapshot_path, "r") as fd: + with open(snapshot_path) as fd: file_content = json.load(fd) return file_content.get(item.nodeid) diff --git a/localstack-core/localstack/testing/scenario/provisioning.py b/localstack-core/localstack/testing/scenario/provisioning.py index cc384d3046c65..c7e116a24a97f 100644 --- a/localstack-core/localstack/testing/scenario/provisioning.py +++ b/localstack-core/localstack/testing/scenario/provisioning.py @@ -1,9 +1,10 @@ import json import logging import warnings -from contextlib import contextmanager +from collections.abc import Callable +from contextlib import AbstractContextManager, contextmanager from pathlib import Path -from typing import TYPE_CHECKING, Callable, ContextManager, Optional +from typing import TYPE_CHECKING import aws_cdk as cdk from botocore.exceptions import ClientError, WaiterError @@ -77,9 +78,9 @@ def __init__( self, aws_client: ServiceLevelClientFactory, namespace: str, - base_path: Optional[str] = None, - force_synth: Optional[bool] = False, - persist_output: Optional[bool] = False, + base_path: str | None = None, + force_synth: bool | None = False, + persist_output: bool | None = False, ): """ :param namespace: repo-unique identifier for this CDK app. @@ -108,8 +109,8 @@ def get_asset_bucket(self): @contextmanager def provisioner( - self, skip_deployment: Optional[bool] = False, skip_teardown: Optional[bool] = False - ) -> ContextManager["InfraProvisioner"]: + self, skip_deployment: bool | None = False, skip_teardown: bool | None = False + ) -> AbstractContextManager["InfraProvisioner"]: """ :param skip_deployment: Set to True to skip stack creation and re-use existing stack without modifications. Also skips custom setup steps. @@ -136,7 +137,7 @@ def my_fixture(infrastructure_setup): else: LOG.debug("Skipping teardown. Resources and stacks are not deleted.") - def provision(self, skip_deployment: Optional[bool] = False): + def provision(self, skip_deployment: bool | None = False): """ Execute all previously added custom provisioning steps and deploy added CDK stacks via CloudFormation. @@ -299,7 +300,7 @@ def teardown(self): def add_cdk_stack( self, cdk_stack: cdk.Stack, - autoclean_buckets: Optional[bool] = True, + autoclean_buckets: bool | None = True, ): """ Register a CDK stack to be deployed in a later `InfraProvisioner.provision` call. @@ -318,7 +319,7 @@ def add_cdk_stack( is_env_true("TEST_CDK_FORCE_SYNTH") or self.force_synth ) # EXPERIMENTAL / API subject to change if not template_path.exists() or should_update_template: - with open(template_path, "wt") as fd: + with open(template_path, "w") as fd: template_json = cdk.assertions.Template.from_stack(cdk_stack).to_json() json.dump(template_json, fd, indent=2) # add trailing newline for linter and Git compliance diff --git a/localstack-core/localstack/testing/snapshots/transformer_utility.py b/localstack-core/localstack/testing/snapshots/transformer_utility.py index 6e6d35ba70689..6f1750bcb8753 100644 --- a/localstack-core/localstack/testing/snapshots/transformer_utility.py +++ b/localstack-core/localstack/testing/snapshots/transformer_utility.py @@ -3,7 +3,7 @@ import re from datetime import datetime from json import JSONDecodeError -from typing import Optional, Pattern +from re import Pattern from localstack_snapshot.snapshots.transformer import ( PATTERN_ISO8601, @@ -50,7 +50,7 @@ class TransformerUtility: @staticmethod def key_value( - key: str, value_replacement: Optional[str] = None, reference_replacement: bool = True + key: str, value_replacement: str | None = None, reference_replacement: bool = True ): """Creates a new KeyValueBasedTransformer. If the key matches, the value will be replaced. diff --git a/localstack-core/localstack/testing/testselection/matching.py b/localstack-core/localstack/testing/testselection/matching.py index 4bf5e9bfaca2d..55fd25b09d844 100644 --- a/localstack-core/localstack/testing/testselection/matching.py +++ b/localstack-core/localstack/testing/testselection/matching.py @@ -2,7 +2,7 @@ import pathlib import re from collections import defaultdict -from typing import Callable, Iterable, Optional +from collections.abc import Callable, Iterable from localstack.aws.scaffold import is_keyword @@ -117,7 +117,7 @@ def prefix(prefix: str) -> Matcher: def generic_service_test_matching_rule( changed_file_path: str, - api_dependencies: Optional[dict[str, Iterable[str]]] = None, + api_dependencies: dict[str, Iterable[str]] | None = None, search_patterns: Iterable[str] = DEFAULT_SEARCH_PATTERNS, test_dirs: Iterable[str] = ("tests/aws/services",), ) -> set[str]: diff --git a/localstack-core/localstack/testing/testselection/opt_out.py b/localstack-core/localstack/testing/testselection/opt_out.py index 32fdf17b9cc26..48d4726633501 100644 --- a/localstack-core/localstack/testing/testselection/opt_out.py +++ b/localstack-core/localstack/testing/testselection/opt_out.py @@ -1,5 +1,5 @@ import fnmatch -from typing import Iterable +from collections.abc import Iterable OPT_OUT = [] diff --git a/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py b/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py index 81571dfb8b1ca..2affcf8e15ee8 100644 --- a/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py +++ b/localstack-core/localstack/testing/testselection/scripts/filter_by_test_selection.py @@ -24,7 +24,7 @@ def main(): sys.exit(1) testselection_file_path = sys.argv[1] - with open(testselection_file_path, "r") as file: + with open(testselection_file_path) as file: selected_tests = [line.strip() for line in file.readlines() if line.strip()] test_files = [line.strip() for line in sys.stdin] filter_test_files(test_files, selected_tests) diff --git a/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py b/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py index 31af5e5d983b5..ca04c1a13cb70 100644 --- a/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py +++ b/localstack-core/localstack/testing/testselection/scripts/generate_test_selection.py @@ -10,8 +10,8 @@ import argparse import os import sys +from collections.abc import Iterable from pathlib import Path -from typing import Iterable from localstack.testing.testselection.git import ( find_merge_base, diff --git a/localstack-core/localstack/testing/testselection/testselection.py b/localstack-core/localstack/testing/testselection/testselection.py index e1a3799cc5c3d..98a5c95d0f012 100644 --- a/localstack-core/localstack/testing/testselection/testselection.py +++ b/localstack-core/localstack/testing/testselection/testselection.py @@ -1,10 +1,10 @@ -from typing import Iterable, Optional +from collections.abc import Iterable from localstack.testing.testselection.matching import MATCHING_RULES, MatchingRule def get_affected_tests_from_changes( - changed_files: Iterable[str], matching_rules: Optional[list[MatchingRule]] = None + changed_files: Iterable[str], matching_rules: list[MatchingRule] | None = None ) -> list[str]: """ Generate test selectors based on the changed files and matching rules to apply to. diff --git a/localstack-core/localstack/utils/analytics/cli.py b/localstack-core/localstack/utils/analytics/cli.py index ebab4f37bb451..a52fe10e3485a 100644 --- a/localstack-core/localstack/utils/analytics/cli.py +++ b/localstack-core/localstack/utils/analytics/cli.py @@ -1,7 +1,6 @@ import datetime import functools from multiprocessing import Process -from typing import List import click @@ -15,7 +14,7 @@ ANALYTICS_API_RESPONSE_TIMEOUT_SECS = 0.5 -def _publish_cmd_as_analytics_event(command_name: str, params: List[str]): +def _publish_cmd_as_analytics_event(command_name: str, params: list[str]): event = Event( name="cli_cmd", payload={"cmd": command_name, "params": params}, @@ -28,7 +27,7 @@ def _publish_cmd_as_analytics_event(command_name: str, params: List[str]): publisher.publish([event]) -def _get_parent_commands(ctx: click.Context) -> List[str]: +def _get_parent_commands(ctx: click.Context) -> list[str]: parent_commands = [] parent = ctx.parent while parent is not None: diff --git a/localstack-core/localstack/utils/analytics/client.py b/localstack-core/localstack/utils/analytics/client.py index 707a5e17e803d..1f7ff8746ed11 100644 --- a/localstack-core/localstack/utils/analytics/client.py +++ b/localstack-core/localstack/utils/analytics/client.py @@ -3,7 +3,7 @@ """ import logging -from typing import Any, Dict, List +from typing import Any import requests @@ -18,10 +18,10 @@ class SessionResponse: - response: Dict[str, Any] + response: dict[str, Any] status: int - def __init__(self, response: Dict[str, Any], status: int = 200): + def __init__(self, response: dict[str, Any], status: int = 200): self.response = response self.status = status @@ -70,7 +70,7 @@ def start_session(self, metadata: ClientMetadata) -> SessionResponse: ) # TODO: naming seems confusing since this doesn't actually append, but directly sends all passed events via HTTP - def append_events(self, events: List[Event]): + def append_events(self, events: list[Event]): # TODO: add compression to append_events # it would maybe be useful to compress analytics data, but it's unclear how that will # affect performance and what the benefit is. need to measure first. @@ -104,7 +104,7 @@ def append_events(self, events: List[Event]): # TODO: Add response type to analytics client return response - def _create_headers(self) -> Dict[str, str]: + def _create_headers(self) -> dict[str, str]: return { "User-Agent": "localstack/" + constants.VERSION, "Localstack-Session-ID": self.localstack_session_id, diff --git a/localstack-core/localstack/utils/analytics/events.py b/localstack-core/localstack/utils/analytics/events.py index 2b30b673b7032..5c81385b81865 100644 --- a/localstack-core/localstack/utils/analytics/events.py +++ b/localstack-core/localstack/utils/analytics/events.py @@ -1,8 +1,8 @@ import abc import dataclasses -from typing import Any, Dict, Union +from typing import Any, Union -EventPayload = Union[Dict[str, Any], Any] # FIXME: better typing +EventPayload = Union[dict[str, Any], Any] # FIXME: better typing @dataclasses.dataclass diff --git a/localstack-core/localstack/utils/analytics/metrics/counter.py b/localstack-core/localstack/utils/analytics/metrics/counter.py index 42dfa5a673e9c..7180a8fb949b9 100644 --- a/localstack-core/localstack/utils/analytics/metrics/counter.py +++ b/localstack-core/localstack/utils/analytics/metrics/counter.py @@ -113,11 +113,11 @@ def __init__(self, namespace: str, name: str, schema_version: int = 1): def collect(self) -> list[CounterPayload]: """Collects the metric unless events are disabled.""" if config.DISABLE_EVENTS: - return list() + return [] if self._count == 0: # Return an empty list if the count is 0, as there are no metrics to send to the analytics backend. - return list() + return [] return [ CounterPayload( @@ -184,7 +184,7 @@ def labels(self, **kwargs: Union[str, float, None]) -> ThreadSafeCounter: def collect(self) -> list[LabeledCounterPayload]: if config.DISABLE_EVENTS: - return list() + return [] payload = [] num_labels = len(self._labels) diff --git a/localstack-core/localstack/utils/analytics/metrics/registry.py b/localstack-core/localstack/utils/analytics/metrics/registry.py index 50f23c345ad67..f88d838d4dd23 100644 --- a/localstack-core/localstack/utils/analytics/metrics/registry.py +++ b/localstack-core/localstack/utils/analytics/metrics/registry.py @@ -43,7 +43,7 @@ class MetricRegistry: Provides methods for retrieving and collecting metrics. """ - _instance: "MetricRegistry" = None + _instance: MetricRegistry = None _mutex: threading.Lock = threading.Lock() def __new__(cls): @@ -57,7 +57,7 @@ def __new__(cls): def __init__(self): if not hasattr(self, "_registry"): - self._registry = dict() + self._registry = {} @property def registry(self) -> dict[MetricRegistryKey, Metric]: diff --git a/localstack-core/localstack/utils/analytics/publisher.py b/localstack-core/localstack/utils/analytics/publisher.py index 48faf6d293625..083e53d77ad98 100644 --- a/localstack-core/localstack/utils/analytics/publisher.py +++ b/localstack-core/localstack/utils/analytics/publisher.py @@ -4,7 +4,7 @@ import threading import time from queue import Full, Queue -from typing import List, Optional +from typing import Optional from localstack import config from localstack.utils.threads import start_thread, start_worker_thread @@ -21,7 +21,7 @@ class Publisher(abc.ABC): A publisher takes a batch of events and publishes them to a destination. """ - def publish(self, events: List[Event]): + def publish(self, events: list[Event]): raise NotImplementedError def close(self): @@ -35,7 +35,7 @@ def __init__(self, client: AnalyticsClient = None) -> None: super().__init__() self.client = client or AnalyticsClient() - def publish(self, events: List[Event]): + def publish(self, events: list[Event]): self.client.append_events(events) def close(self): @@ -47,7 +47,7 @@ class Printer(Publisher): Publisher that prints serialized events to stdout. """ - def publish(self, events: List[Event]): + def publish(self, events: list[Event]): for event in events: print(event.asdict()) diff --git a/localstack-core/localstack/utils/analytics/service_request_aggregator.py b/localstack-core/localstack/utils/analytics/service_request_aggregator.py index f503235201c45..2b0bdd590db2f 100644 --- a/localstack-core/localstack/utils/analytics/service_request_aggregator.py +++ b/localstack-core/localstack/utils/analytics/service_request_aggregator.py @@ -2,7 +2,7 @@ import logging import threading from collections import Counter -from typing import Dict, List, NamedTuple, Optional +from typing import NamedTuple, Optional from localstack import config from localstack.runtime.shutdown import SHUTDOWN_HANDLERS @@ -110,7 +110,7 @@ def _create_analytics_payload(self): "api_calls": self._aggregate_api_calls(self.counter), } - def _aggregate_api_calls(self, counter) -> List: + def _aggregate_api_calls(self, counter) -> list: aggregations = [] for api_call_info, count in counter.items(): doc = api_call_info._asdict() @@ -121,5 +121,5 @@ def _aggregate_api_calls(self, counter) -> List: aggregations.append(doc) return aggregations - def _emit_payload(self, analytics_payload: Dict): + def _emit_payload(self, analytics_payload: dict): analytics.log.event(EVENT_NAME, analytics_payload) diff --git a/localstack-core/localstack/utils/archives.py b/localstack-core/localstack/utils/archives.py index e3b3673541a80..7503bbe8a8667 100644 --- a/localstack-core/localstack/utils/archives.py +++ b/localstack-core/localstack/utils/archives.py @@ -233,7 +233,7 @@ def download_and_extract( rm_rf(tmp_archive) raise e - if ext == ".zip": + if ext in (".zip", ".whl"): unzip(tmp_archive, target_dir) elif ext in ( ".bz2", diff --git a/localstack-core/localstack/utils/aws/arns.py b/localstack-core/localstack/utils/aws/arns.py index 5b6f139473bac..76e6fc568d752 100644 --- a/localstack-core/localstack/utils/aws/arns.py +++ b/localstack-core/localstack/utils/aws/arns.py @@ -155,14 +155,14 @@ def secretsmanager_secret_arn( def cloudformation_stack_arn( stack_name: str, stack_id: str, account_id: str, region_name: str ) -> str: - pattern = "arn:%s:cloudformation:%s:%s:stack/%s/{stack_id}".format(stack_id=stack_id) + pattern = f"arn:%s:cloudformation:%s:%s:stack/%s/{stack_id}" return _resource_arn(stack_name, pattern, account_id=account_id, region_name=region_name) def cloudformation_change_set_arn( change_set_name: str, change_set_id: str, account_id: str, region_name: str ) -> str: - pattern = "arn:%s:cloudformation:%s:%s:changeSet/%s/{cs_id}".format(cs_id=change_set_id) + pattern = f"arn:%s:cloudformation:%s:%s:changeSet/%s/{change_set_id}" return _resource_arn(change_set_name, pattern, account_id=account_id, region_name=region_name) diff --git a/localstack-core/localstack/utils/aws/aws_responses.py b/localstack-core/localstack/utils/aws/aws_responses.py index 509b7a8a32889..b33f51933b834 100644 --- a/localstack-core/localstack/utils/aws/aws_responses.py +++ b/localstack-core/localstack/utils/aws/aws_responses.py @@ -3,7 +3,7 @@ import json import re from binascii import crc32 -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union from urllib.parse import parse_qs import xmltodict @@ -43,14 +43,12 @@ def requests_error_response_xml( ): response = RequestsResponse() xmlns = xmlns or "http://%s.amazonaws.com/doc/2010-03-31/" % service - response._content = """ + response._content = f""" Sender {code_string} {message} - {req_id} - """.format( - xmlns=xmlns, message=message, code_string=code_string, req_id=short_uid() - ) + {short_uid()} + """ response.status_code = code return response @@ -65,18 +63,13 @@ def requests_error_response_xml_signature_calculation( aws_access_token="temp", ): response = RequestsResponse() - response_template = """ + response_template = f""" {code_string} {message} - {req_id} - {host_id} - """.format( - message=message, - code_string=code_string, - req_id=short_uid(), - host_id=short_uid(), - ) + {short_uid()} + {short_uid()} + """ parsed_response = xmltodict.parse(response_template) response.status_code = code @@ -94,8 +87,8 @@ def requests_error_response_xml_signature_calculation( server_time = datetime.datetime.utcnow().isoformat()[:-4] expires_isoformat = datetime.datetime.fromtimestamp(int(expires)).isoformat()[:-4] parsed_response["Error"]["Code"] = code_string - parsed_response["Error"]["Expires"] = "{}Z".format(expires_isoformat) - parsed_response["Error"]["ServerTime"] = "{}Z".format(server_time) + parsed_response["Error"]["Expires"] = f"{expires_isoformat}Z" + parsed_response["Error"]["ServerTime"] = f"{server_time}Z" set_response_content(response, xmltodict.unparse(parsed_response)) if not signature and not expires and code_string == "AccessDenied": @@ -106,7 +99,7 @@ def requests_error_response_xml_signature_calculation( def requests_error_response( - req_headers: Dict, + req_headers: dict, message: Union[str, bytes], code: int = 500, error_type: str = "InternalFailure", @@ -121,7 +114,7 @@ def requests_error_response( ) -def is_json_request(req_headers: Dict) -> bool: +def is_json_request(req_headers: dict) -> bool: ctype = req_headers.get("Content-Type", "") accept = req_headers.get("Accept", "") return "json" in ctype or "json" in accept @@ -186,7 +179,7 @@ def set_response_content(response, content, headers=None): response.headers["Content-Length"] = str(len(response._content)) -def create_sqs_system_attributes(headers: Dict[str, str]) -> Dict[str, Any]: +def create_sqs_system_attributes(headers: dict[str, str]) -> dict[str, Any]: system_attributes = {} if "X-Amzn-Trace-Id" in headers: system_attributes["AWSTraceHeader"] = { @@ -196,7 +189,7 @@ def create_sqs_system_attributes(headers: Dict[str, str]) -> Dict[str, Any]: return system_attributes -def parse_query_string(url_or_qs: str, multi_values=False) -> Dict[str, str]: +def parse_query_string(url_or_qs: str, multi_values=False) -> dict[str, str]: url_or_qs = str(url_or_qs or "").strip() # we match if the `url_or_qs` passed is maybe a URL if regex_url_start.match(url_or_qs) and "?" not in url_or_qs: diff --git a/localstack-core/localstack/utils/aws/aws_stack.py b/localstack-core/localstack/utils/aws/aws_stack.py index 8ca6107337b49..2d105f11269b3 100644 --- a/localstack-core/localstack/utils/aws/aws_stack.py +++ b/localstack-core/localstack/utils/aws/aws_stack.py @@ -2,7 +2,7 @@ import re import socket from functools import lru_cache -from typing import List, Union +from typing import Union import boto3 @@ -20,7 +20,7 @@ CACHE_S3_HOSTNAME_DNS_STATUS = None -@lru_cache() +@lru_cache def get_valid_regions(): session = boto3.Session() valid_regions = set() @@ -32,7 +32,7 @@ def get_valid_regions(): # FIXME: AWS recommends use of SSM parameter store to determine per region availability # https://github.com/aws/aws-sdk/issues/206#issuecomment-1471354853 -@lru_cache() +@lru_cache def get_valid_regions_for_service(service_name): session = boto3.Session() regions = list(session.get_available_regions(service_name)) @@ -60,7 +60,7 @@ def get_s3_hostname(): try: assert socket.gethostbyname(S3_VIRTUAL_HOSTNAME) CACHE_S3_HOSTNAME_DNS_STATUS = True - except socket.error: + except OSError: CACHE_S3_HOSTNAME_DNS_STATUS = False if CACHE_S3_HOSTNAME_DNS_STATUS: return S3_VIRTUAL_HOSTNAME @@ -68,7 +68,7 @@ def get_s3_hostname(): def fix_account_id_in_arns( - response, replacement: str, colon_delimiter: str = ":", existing: Union[str, List[str]] = None + response, replacement: str, colon_delimiter: str = ":", existing: Union[str, list[str]] = None ): """Fix the account ID in the ARNs returned in the given Flask response or string""" from moto.core import DEFAULT_ACCOUNT_ID @@ -78,13 +78,9 @@ def fix_account_id_in_arns( is_str_obj = is_string_or_bytes(response) content = to_str(response if is_str_obj else response._content) - replacement = r"arn{col}aws{col}\1{col}\2{col}{acc}{col}".format( - col=colon_delimiter, acc=replacement - ) + replacement = rf"arn{colon_delimiter}aws{colon_delimiter}\1{colon_delimiter}\2{colon_delimiter}{replacement}{colon_delimiter}" for acc_id in existing: - regex = r"arn{col}aws{col}([^:%]+){col}([^:%]*){col}{acc}{col}".format( - col=colon_delimiter, acc=acc_id - ) + regex = rf"arn{colon_delimiter}aws{colon_delimiter}([^:%]+){colon_delimiter}([^:%]*){colon_delimiter}{acc_id}{colon_delimiter}" content = re.sub(regex, replacement, content) if not is_str_obj: diff --git a/localstack-core/localstack/utils/aws/dead_letter_queue.py b/localstack-core/localstack/utils/aws/dead_letter_queue.py index 9fdd8c70ec5e3..0896dfc4d5d4a 100644 --- a/localstack-core/localstack/utils/aws/dead_letter_queue.py +++ b/localstack-core/localstack/utils/aws/dead_letter_queue.py @@ -1,7 +1,6 @@ import json import logging import uuid -from typing import Dict, List from localstack.aws.connect import connect_to from localstack.utils.aws import arns @@ -34,7 +33,7 @@ def sns_error_to_dead_letter_queue( return _send_to_dead_letter_queue(sns_subscriber["SubscriptionArn"], target_arn, event, error) -def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Dict, error, role: str = None): +def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: dict, error, role: str = None): if not dlq_arn: return LOG.info("Sending failed execution %s to dead letter queue %s", source_arn, dlq_arn) @@ -84,7 +83,7 @@ def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Dict, error return dlq_arn -def _prepare_messages_to_dlq(source_arn: str, event: Dict, error) -> List[Dict]: +def _prepare_messages_to_dlq(source_arn: str, event: dict, error) -> list[dict]: messages = [] custom_attrs = { "RequestID": {"DataType": "String", "StringValue": str(uuid.uuid4())}, @@ -136,7 +135,7 @@ def _prepare_messages_to_dlq(source_arn: str, event: Dict, error) -> List[Dict]: return messages -def message_attributes_to_upper(message_attrs: Dict) -> Dict: +def message_attributes_to_upper(message_attrs: dict) -> dict: """Convert message attribute details (first characters) to upper case (e.g., StringValue, DataType).""" message_attrs = message_attrs or {} for _, attr in message_attrs.items(): diff --git a/localstack-core/localstack/utils/aws/message_forwarding.py b/localstack-core/localstack/utils/aws/message_forwarding.py index ad28c015b9485..d4ca0d0df445c 100644 --- a/localstack-core/localstack/utils/aws/message_forwarding.py +++ b/localstack-core/localstack/utils/aws/message_forwarding.py @@ -3,7 +3,7 @@ import logging import re import uuid -from typing import Dict, Optional +from typing import Optional from moto.events.models import events_backends @@ -31,10 +31,10 @@ # TODO: refactor/split this. too much here is service specific def send_event_to_target( target_arn: str, - event: Dict, - target_attributes: Dict = None, + event: dict, + target_attributes: dict = None, asynchronous: bool = True, - target: Dict = None, + target: dict = None, role: str = None, source_arn: str = None, source_service: str = None, @@ -155,7 +155,7 @@ def send_event_to_target( LOG.warning('Unsupported Events rule target ARN: "%s"', target_arn) -def auth_keys_from_connection(connection: Dict): +def auth_keys_from_connection(connection: dict): headers = {} auth_type = connection.get("AuthorizationType").upper() @@ -164,9 +164,7 @@ def auth_keys_from_connection(connection: Dict): basic_auth_parameters = auth_parameters.get("BasicAuthParameters", {}) username = basic_auth_parameters.get("Username", "") password = basic_auth_parameters.get("Password", "") - auth = "Basic " + to_str( - base64.b64encode("{}:{}".format(username, password).encode("ascii")) - ) + auth = "Basic " + to_str(base64.b64encode(f"{username}:{password}".encode("ascii"))) headers.update({"authorization": auth}) if auth_type == AUTH_API_KEY: @@ -206,7 +204,7 @@ def auth_keys_from_connection(connection: Dict): token_type = oauth_data.get("token_type", "") access_token = oauth_data.get("access_token", "") - auth_header = "{} {}".format(token_type, access_token) + auth_header = f"{token_type} {access_token}" headers.update({"authorization": auth_header}) return headers @@ -216,7 +214,7 @@ def list_of_parameters_to_object(items): return {item.get("Key"): item.get("Value") for item in items} -def send_event_to_api_destination(target_arn, event, http_parameters: Optional[Dict] = None): +def send_event_to_api_destination(target_arn, event, http_parameters: Optional[dict] = None): """Send an event to an EventBridge API destination See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html""" @@ -291,7 +289,7 @@ def add_api_destination_authorization(destination, headers, event): return endpoint -def add_target_http_parameters(http_parameters: Dict, endpoint: str, headers: Dict, body): +def add_target_http_parameters(http_parameters: dict, endpoint: str, headers: dict, body): endpoint = add_path_parameters_to_url(endpoint, http_parameters.get("PathParameterValues", [])) # The request should prioritze connection header/query parameters over target params if there is an overlap diff --git a/localstack-core/localstack/utils/aws/request_context.py b/localstack-core/localstack/utils/aws/request_context.py index 9af869aeffa19..10a4c38b337d6 100644 --- a/localstack-core/localstack/utils/aws/request_context.py +++ b/localstack-core/localstack/utils/aws/request_context.py @@ -4,7 +4,7 @@ import logging import re -from typing import Dict, Optional +from typing import Optional from rolo import Request as RoloRequest @@ -43,7 +43,7 @@ def extract_account_id_from_auth_header(headers) -> Optional[str]: return get_account_id_from_access_key_id(access_key_id) -def extract_access_key_id_from_auth_header(headers: Dict[str, str]) -> Optional[str]: +def extract_access_key_id_from_auth_header(headers: dict[str, str]) -> Optional[str]: auth = headers.get("Authorization") or "" if auth.startswith("AWS4-"): @@ -67,7 +67,7 @@ def extract_region_from_headers(headers) -> str: return extract_region_from_auth_header(headers) or AWS_REGION_US_EAST_1 -def extract_service_name_from_auth_header(headers: Dict) -> Optional[str]: +def extract_service_name_from_auth_header(headers: dict) -> Optional[str]: try: auth_header = headers.get("authorization", "") credential_scope = auth_header.split(",")[0].split()[1] @@ -79,7 +79,7 @@ def extract_service_name_from_auth_header(headers: Dict) -> Optional[str]: def mock_aws_request_headers( service: str, aws_access_key_id: str, region_name: str, internal: bool = False -) -> Dict[str, str]: +) -> dict[str, str]: """ Returns a mock set of headers that resemble SigV4 signing method. """ diff --git a/localstack-core/localstack/utils/aws/templating.py b/localstack-core/localstack/utils/aws/templating.py index 4d9ef57897da1..f0da26536ee03 100644 --- a/localstack-core/localstack/utils/aws/templating.py +++ b/localstack-core/localstack/utils/aws/templating.py @@ -1,6 +1,6 @@ import json import re -from typing import Any, Dict +from typing import Any import airspeed @@ -44,7 +44,7 @@ def qr(self, *args, **kwargs): class VtlTemplate: """Utility class for rendering Velocity templates""" - def render_vtl(self, template: str, variables: Dict, as_json=False) -> str | dict: + def render_vtl(self, template: str, variables: dict, as_json=False) -> str | dict: """ Render the given VTL template against the dict of variables. Note that this is a potentially mutating operation which may change the values of `variables` in-place. @@ -125,7 +125,7 @@ def apply(obj, **_): rendered_template = json.loads(rendered_template) return rendered_template - def prepare_namespace(self, variables: Dict[str, Any], source: str = "") -> Dict: + def prepare_namespace(self, variables: dict[str, Any], source: str = "") -> dict: namespace = dict(variables or {}) namespace.setdefault("context", {}) if not namespace.get("util"): diff --git a/localstack-core/localstack/utils/batch_policy.py b/localstack-core/localstack/utils/batch_policy.py index 9ac5e575f3a49..1c7f4fa18dfde 100644 --- a/localstack-core/localstack/utils/batch_policy.py +++ b/localstack-core/localstack/utils/batch_policy.py @@ -1,6 +1,6 @@ import copy import time -from typing import Generic, List, Optional, TypeVar, overload +from typing import Generic, Optional, TypeVar, overload from pydantic import Field from pydantic.dataclasses import dataclass @@ -76,7 +76,7 @@ def _check_batch_policy(self) -> bool: def add(self, item: T, *, deep_copy: bool = False) -> BatchPolicyTriggered: ... @overload - def add(self, items: List[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: ... + def add(self, items: list[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: ... def add(self, item_or_items: T | list[T], *, deep_copy: bool = False) -> BatchPolicyTriggered: """ diff --git a/localstack-core/localstack/utils/bootstrap.py b/localstack-core/localstack/utils/bootstrap.py index 536f683d334cc..54943abb07c5d 100644 --- a/localstack-core/localstack/utils/bootstrap.py +++ b/localstack-core/localstack/utils/bootstrap.py @@ -9,8 +9,9 @@ import signal import threading import time +from collections.abc import Iterable from functools import wraps -from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Union +from typing import Any, Callable, Optional, Union from localstack import config, constants from localstack.config import ( @@ -156,7 +157,7 @@ def wrapped(*args, **kwargs): return wrapper -def get_docker_image_details(image_name: str = None) -> Dict[str, str]: +def get_docker_image_details(image_name: str = None) -> dict[str, str]: image_name = image_name or get_docker_image_to_start() try: result = DOCKER_CLIENT.inspect_image(image_name) @@ -249,7 +250,7 @@ def setup_logging(): # -------------- -def resolve_apis(services: Iterable[str]) -> Set[str]: +def resolve_apis(services: Iterable[str]) -> set[str]: """ Resolves recursively for the given collection of services (e.g., ["serverless", "cognito"]) the list of actual API services that need to be included (e.g., {'dynamodb', 'cloudformation', 'logs', 'kinesis', 'sts', @@ -289,8 +290,8 @@ def resolve_apis(services: Iterable[str]) -> Set[str]: return result -@functools.lru_cache() -def get_enabled_apis() -> Set[str]: +@functools.lru_cache +def get_enabled_apis() -> set[str]: """ Returns the list of APIs that are enabled through the combination of the SERVICES variable and STRICT_SERVICE_LOADING variable. If the SERVICES variable is empty, then it will return all available services. @@ -324,8 +325,8 @@ def is_api_enabled(api: str) -> bool: return api in get_enabled_apis() -@functools.lru_cache() -def get_preloaded_services() -> Set[str]: +@functools.lru_cache +def get_preloaded_services() -> set[str]: """ Returns the list of APIs that are marked to be eager loaded through the combination of SERVICES variable and EAGER_SERVICE_LOADING. If the SERVICES variable is empty, then it will return all available services. @@ -431,10 +432,8 @@ def port_exposed(port): if not port_exposed(edge_port): warns.append( - ( - f"Edge port {edge_port} is not exposed. You may have to add the entry " - 'to the "ports" section of the docker-compose file.' - ) + f"Edge port {edge_port} is not exposed. You may have to add the entry " + 'to the "ports" section of the docker-compose file.' ) # print warning/info messages @@ -638,7 +637,7 @@ def _cfg(cfg: ContainerConfiguration): return _cfg @staticmethod - def custom_command(cmd: List[str]): + def custom_command(cmd: list[str]): """ Overwrites the container command and unsets the default entrypoint. @@ -653,7 +652,7 @@ def _cfg(cfg: ContainerConfiguration): return _cfg @staticmethod - def env_vars(env_vars: Dict[str, str]): + def env_vars(env_vars: dict[str, str]): def _cfg(cfg: ContainerConfiguration): cfg.env_vars.update(env_vars) @@ -674,7 +673,7 @@ def _cfg(cfg: ContainerConfiguration): return _cfg @staticmethod - def cli_params(params: Dict[str, Any]): + def cli_params(params: dict[str, Any]): """ Parse docker CLI parameters and add them to the config. The currently known CLI params are:: @@ -808,7 +807,7 @@ def get_gateway_port(container: Container) -> int: :param container: the localstack container :return: the gateway port reachable from the host """ - candidates: List[int] + candidates: list[int] gateway_listen = container.config.env_vars.get("GATEWAY_LISTEN") if gateway_listen: @@ -1001,7 +1000,7 @@ def shutdown(self, timeout: int = 10, remove: bool = True): return raise - def inspect(self) -> Dict[str, Union[Dict, str]]: + def inspect(self) -> dict[str, Union[dict, str]]: return self.container_client.inspect_container(container_name_or_id=self.id) def attach(self): @@ -1033,7 +1032,7 @@ def __init__(self, container: Container, callback: Callable[[str], None] = print def _can_start_streaming(self): if self._closed.is_set(): - raise IOError("Already stopped") + raise OSError("Already stopped") if not self.container.running_container: return False return self.container.running_container.is_running() @@ -1041,7 +1040,7 @@ def _can_start_streaming(self): def run(self): try: poll_condition(self._can_start_streaming) - except IOError: + except OSError: return self._stream = self.container.running_container.stream_logs() for line in self._stream: @@ -1227,7 +1226,7 @@ def prepare_host(console): hooks.prepare_host.run() -def start_infra_in_docker(console, cli_params: Dict[str, Any] = None): +def start_infra_in_docker(console, cli_params: dict[str, Any] = None): prepare_docker_start() # create and prepare container @@ -1302,7 +1301,7 @@ def ensure_container_image(console, container: Container): console.log("download complete") -def start_infra_in_docker_detached(console, cli_params: Dict[str, Any] = None): +def start_infra_in_docker_detached(console, cli_params: dict[str, Any] = None): """ An alternative to start_infra_in_docker where the terminal is not blocked by the follow on the logfile. """ diff --git a/localstack-core/localstack/utils/collections.py b/localstack-core/localstack/utils/collections.py index 41860cd9a190c..e129be52727ef 100644 --- a/localstack-core/localstack/utils/collections.py +++ b/localstack-core/localstack/utils/collections.py @@ -5,18 +5,11 @@ import logging import re -from collections.abc import Mapping +from collections.abc import Iterable, Iterator, Mapping, Sized from typing import ( Any, Callable, - Dict, - Iterable, - Iterator, - List, Optional, - Sized, - Tuple, - Type, TypedDict, TypeVar, Union, @@ -40,7 +33,7 @@ class AccessTrackingDict(dict): simply duplicates the entries of "wrapped" in the constructor, for simplicity. """ - def __init__(self, wrapped, callback: Callable[[Dict, str, List, Dict], Any] = None): + def __init__(self, wrapped, callback: Callable[[dict, str, list, dict], Any] = None): super().__init__(wrapped) self.callback = callback @@ -112,7 +105,7 @@ def __hash__(self): _ListType = TypeVar("_ListType") -class PaginatedList(List[_ListType]): +class PaginatedList(list[_ListType]): """List which can be paginated and filtered. For usage in AWS APIs with paginated responses""" DEFAULT_PAGE_SIZE = 50 @@ -123,7 +116,7 @@ def get_page( next_token: str = None, page_size: int = None, filter_function: Callable[[_ListType], bool] = None, - ) -> Tuple[List[_ListType], Optional[str]]: + ) -> tuple[list[_ListType], Optional[str]]: if filter_function is not None: result_list = list(filter(filter_function, self)) else: @@ -280,13 +273,13 @@ def pick_attributes(dictionary, paths): return new_dictionary -def select_attributes(obj: Dict, attributes: List[str]) -> Dict: +def select_attributes(obj: dict, attributes: list[str]) -> dict: """Select a subset of attributes from the given dict (returns a copy)""" attributes = attributes if is_list_or_tuple(attributes) else [attributes] return {k: v for k, v in obj.items() if k in attributes} -def remove_attributes(obj: Dict, attributes: List[str], recursive: bool = False) -> Dict: +def remove_attributes(obj: dict, attributes: list[str], recursive: bool = False) -> dict: """Remove a set of attributes from the given dict (in-place)""" from localstack.utils.objects import recurse_object @@ -306,8 +299,8 @@ def _remove(o, **kwargs): def rename_attributes( - obj: Dict, old_to_new_attributes: Dict[str, str], in_place: bool = False -) -> Dict: + obj: dict, old_to_new_attributes: dict[str, str], in_place: bool = False +) -> dict: """Rename a set of attributes in the given dict object. Second parameter is a dict that maps old to new attribute names. Default is to return a copy, but can also pass in_place=True.""" if not in_place: @@ -322,7 +315,7 @@ def is_list_or_tuple(obj) -> bool: return isinstance(obj, (list, tuple)) -def ensure_list(obj: Any, wrap_none=False) -> Optional[List]: +def ensure_list(obj: Any, wrap_none=False) -> Optional[list]: """Wrap the given object in a list, or return the object itself if it already is a list.""" if obj is None and not wrap_none: return obj @@ -385,7 +378,7 @@ def merge_dicts(*dicts, **kwargs): return result -def remove_none_values_from_dict(dict: Dict) -> Dict: +def remove_none_values_from_dict(dict: dict) -> dict: return {k: v for (k, v) in dict.items() if v is not None} @@ -399,7 +392,7 @@ def last_index_of(array, value): return result -def is_sub_dict(child_dict: Dict, parent_dict: Dict) -> bool: +def is_sub_dict(child_dict: dict, parent_dict: dict) -> bool: """Returns whether the first dict is a sub-dict (subset) of the second dict.""" return all(parent_dict.get(key) == val for key, val in child_dict.items()) @@ -429,7 +422,7 @@ def is_none_or_empty(obj: Union[Optional[str], Optional[list]]) -> bool: ) -def select_from_typed_dict(typed_dict: Type[TypedDict], obj: Dict, filter: bool = False) -> Dict: +def select_from_typed_dict(typed_dict: type[TypedDict], obj: dict, filter: bool = False) -> dict: """ Select a subset of attributes from a dictionary based on the keys of a given `TypedDict`. :param typed_dict: the `TypedDict` blueprint @@ -445,10 +438,10 @@ def select_from_typed_dict(typed_dict: Type[TypedDict], obj: Dict, filter: bool return selection -T = TypeVar("T", bound=Dict) +T = TypeVar("T", bound=dict) -def convert_to_typed_dict(typed_dict: Type[T], obj: Dict, strict: bool = False) -> T: +def convert_to_typed_dict(typed_dict: type[T], obj: dict, strict: bool = False) -> T: """ Converts the given object to the given typed dict (by calling the type constructors). Limitations: @@ -482,7 +475,7 @@ def convert_to_typed_dict(typed_dict: Type[T], obj: Dict, strict: bool = False) return result -def dict_multi_values(elements: Union[List, Dict]) -> Dict[str, List[Any]]: +def dict_multi_values(elements: Union[list, dict]) -> dict[str, list[Any]]: """ Return a dictionary with the original keys from the list of dictionary and the values are the list of values of the original dictionary. @@ -511,7 +504,7 @@ def dict_multi_values(elements: Union[List, Dict]) -> Dict[str, List[Any]]: def split_list_by( lst: Iterable[ItemType], predicate: Callable[[ItemType], bool] -) -> Tuple[List[ItemType], List[ItemType]]: +) -> tuple[list[ItemType], list[ItemType]]: truthy, falsy = [], [] for item in lst: @@ -534,3 +527,17 @@ def is_comma_delimited_list(string: str, item_regex: Optional[str] = None) -> bo if pattern.match(string) is None: return False return True + + +_E = TypeVar("_E") + + +def optional_list(condition: bool, items: Iterable[_E]) -> list[_E]: + """ + Given an iterable, either create a list out of the entire iterable (if `condition` is `True`), or return the empty list. + >>> print(optional_list(True, [1, 2, 3])) + [1, 2, 3] + >>> print(optional_list(False, [1, 2, 3])) + [] + """ + return list(filter(lambda _: condition, items)) diff --git a/localstack-core/localstack/utils/config_listener.py b/localstack-core/localstack/utils/config_listener.py index c8678baede5ae..c410c2e667240 100644 --- a/localstack-core/localstack/utils/config_listener.py +++ b/localstack-core/localstack/utils/config_listener.py @@ -1,7 +1,7 @@ import json import logging import re -from typing import Callable, List +from typing import Callable from requests.models import Response @@ -9,7 +9,7 @@ LOG = logging.getLogger(__name__) -CONFIG_LISTENERS: List[Callable[[str, str], None]] = [] +CONFIG_LISTENERS: list[Callable[[str, str], None]] = [] def trigger_config_listeners(variable, new_value): diff --git a/localstack-core/localstack/utils/container_networking.py b/localstack-core/localstack/utils/container_networking.py index 2e54dec0672ba..1a734576fa035 100644 --- a/localstack-core/localstack/utils/container_networking.py +++ b/localstack-core/localstack/utils/container_networking.py @@ -12,7 +12,7 @@ LOG = logging.getLogger(__name__) -@lru_cache() +@lru_cache def get_main_container_network() -> Optional[str]: """ Gets the main network of the LocalStack container (if we run in one, bridge otherwise) @@ -49,7 +49,7 @@ def get_main_container_network() -> Optional[str]: return main_container_network -@lru_cache() +@lru_cache def get_endpoint_for_network(network: Optional[str] = None) -> str: """ Get the LocalStack endpoint (= IP address) on the given network. @@ -126,7 +126,7 @@ def get_main_container_id(): return None -@lru_cache() +@lru_cache def get_main_container_name(): """ Returns the container name of the LocalStack container diff --git a/localstack-core/localstack/utils/container_utils/container_client.py b/localstack-core/localstack/utils/container_utils/container_client.py index baac120b6ff3c..36ac6c4729120 100644 --- a/localstack-core/localstack/utils/container_utils/container_client.py +++ b/localstack-core/localstack/utils/container_utils/container_client.py @@ -13,13 +13,10 @@ from pathlib import Path from typing import ( Callable, - Dict, - List, Literal, NamedTuple, Optional, Protocol, - Tuple, TypedDict, Union, get_args, @@ -157,7 +154,7 @@ def __repr__(self): # defines the type for port mappings (source->target port range) -PortRange = Union[List, HashableList] +PortRange = Union[list, HashableList] # defines the protocol for a port range ("tcp" or "udp") PortProtocol = str @@ -176,7 +173,7 @@ class PortMappings: # bind host to be used for defining port mappings bind_host: str # maps `from` port range to `to` port range for port mappings - mappings: Dict[Tuple[PortRange, PortProtocol], List] + mappings: dict[tuple[PortRange, PortProtocol], list] def __init__(self, bind_host: str = None): self.bind_host = bind_host if bind_host else "" @@ -253,7 +250,7 @@ def entry(k, v): return " ".join([entry(k, v) for k, v in self.mappings.items()]) - def to_list(self) -> List[str]: # TODO test + def to_list(self) -> list[str]: # TODO test bind_address = f"{self.bind_host}:" if self.bind_host else "" def entry(k, v): @@ -269,7 +266,7 @@ def entry(k, v): return [item for k, v in self.mappings.items() for item in entry(k, v)] - def to_dict(self) -> Dict[str, Union[Tuple[str, Union[int, List[int]]], int]]: + def to_dict(self) -> dict[str, Union[tuple[str, Union[int, list[int]]], int]]: bind_address = self.bind_host or "" def bind_port(bind_address, host_port): @@ -367,7 +364,7 @@ def __repr__(self): return f"" -SimpleVolumeBind = Tuple[str, str] +SimpleVolumeBind = tuple[str, str] """Type alias for a simple version of VolumeBind""" @@ -455,9 +452,9 @@ def to_docker_sdk_parameters(self) -> tuple[str, dict[str, str]]: class VolumeMappings: - mappings: List[Union[SimpleVolumeBind, BindMount]] + mappings: list[Union[SimpleVolumeBind, BindMount]] - def __init__(self, mappings: List[Union[SimpleVolumeBind, BindMount, VolumeDirMount]] = None): + def __init__(self, mappings: list[Union[SimpleVolumeBind, BindMount, VolumeDirMount]] = None): self.mappings = mappings if mappings is not None else [] def add(self, mapping: Union[SimpleVolumeBind, BindMount, VolumeDirMount]): @@ -521,7 +518,7 @@ class VolumeInfo(NamedTuple): @dataclasses.dataclass class LogConfig: type: Literal["json-file", "syslog", "journald", "gelf", "fluentd", "none", "awslogs", "splunk"] - config: Dict[str, str] = dataclasses.field(default_factory=dict) + config: dict[str, str] = dataclasses.field(default_factory=dict) @dataclasses.dataclass @@ -530,11 +527,11 @@ class ContainerConfiguration: name: Optional[str] = None volumes: VolumeMappings = dataclasses.field(default_factory=VolumeMappings) ports: PortMappings = dataclasses.field(default_factory=PortMappings) - exposed_ports: List[str] = dataclasses.field(default_factory=list) - entrypoint: Optional[Union[List[str], str]] = None + exposed_ports: list[str] = dataclasses.field(default_factory=list) + entrypoint: Optional[Union[list[str], str]] = None additional_flags: Optional[str] = None - command: Optional[List[str]] = None - env_vars: Dict[str, str] = dataclasses.field(default_factory=dict) + command: Optional[list[str]] = None + env_vars: dict[str, str] = dataclasses.field(default_factory=dict) privileged: bool = False remove: bool = False @@ -544,15 +541,15 @@ class ContainerConfiguration: stdin: Optional[str] = None user: Optional[str] = None - cap_add: Optional[List[str]] = None - cap_drop: Optional[List[str]] = None - security_opt: Optional[List[str]] = None + cap_add: Optional[list[str]] = None + cap_drop: Optional[list[str]] = None + security_opt: Optional[list[str]] = None network: Optional[str] = None dns: Optional[str] = None workdir: Optional[str] = None platform: Optional[str] = None - ulimits: Optional[List[Ulimit]] = None - labels: Optional[Dict[str, str]] = None + ulimits: Optional[list[Ulimit]] = None + labels: Optional[dict[str, str]] = None init: Optional[bool] = None log_config: Optional[LogConfig] = None @@ -577,17 +574,17 @@ class DockerRunFlags: create: https://docs.docker.com/engine/reference/commandline/create/ """ - env_vars: Optional[Dict[str, str]] - extra_hosts: Optional[Dict[str, str]] - labels: Optional[Dict[str, str]] - volumes: Optional[List[SimpleVolumeBind]] + env_vars: Optional[dict[str, str]] + extra_hosts: Optional[dict[str, str]] + labels: Optional[dict[str, str]] + volumes: Optional[list[SimpleVolumeBind]] network: Optional[str] platform: Optional[DockerPlatform] privileged: Optional[bool] ports: Optional[PortMappings] - ulimits: Optional[List[Ulimit]] + ulimits: Optional[list[Ulimit]] user: Optional[str] - dns: Optional[List[str]] + dns: Optional[list[str]] class RegistryResolverStrategy(Protocol): @@ -621,7 +618,7 @@ def get_container_stats(self, container_name: str) -> DockerContainerStats: """Returns the usage statistics of the container with the given name""" pass - def get_networks(self, container_name: str) -> List[str]: + def get_networks(self, container_name: str) -> list[str]: LOG.debug("Getting networks for container: %s", container_name) container_attrs = self.inspect_container(container_name_or_id=container_name) return list(container_attrs["NetworkSettings"].get("Networks", {}).keys()) @@ -701,13 +698,13 @@ def remove_image(self, image: str, force: bool = True) -> None: """ @abstractmethod - def list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: + def list_containers(self, filter: Union[list[str], str, None] = None, all=True) -> list[dict]: """List all containers matching the given filters :return: A list of dicts with keys id, image, name, labels, status """ - def get_running_container_names(self) -> List[str]: + def get_running_container_names(self) -> list[str]: """Returns a list of the names of all running containers""" result = self.list_containers(all=False) result = [container["name"] for container in result] @@ -798,7 +795,7 @@ def get_docker_image_names( strip_latest: bool = True, include_tags: bool = True, strip_wellknown_repo_prefixes: bool = True, - ) -> List[str]: + ) -> list[str]: """ Get all names of docker images available to the container engine :param strip_latest: return images both with and without :latest tag @@ -817,13 +814,13 @@ def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: """Returns a blocking generator you can iterate over to retrieve log output as it happens.""" @abstractmethod - def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: + def inspect_container(self, container_name_or_id: str) -> dict[str, Union[dict, str]]: """Get detailed attributes of a container. :return: Dict containing docker attributes as returned by the daemon """ - def inspect_container_volumes(self, container_name_or_id) -> List[VolumeInfo]: + def inspect_container_volumes(self, container_name_or_id) -> list[VolumeInfo]: """Return information about the volumes mounted into the given container. :param container_name_or_id: the container name or id @@ -838,7 +835,7 @@ def inspect_container_volumes(self, container_name_or_id) -> List[VolumeInfo]: @abstractmethod def inspect_image( self, image_name: str, pull: bool = True, strip_wellknown_repo_prefixes: bool = True - ) -> Dict[str, Union[dict, list, str]]: + ) -> dict[str, Union[dict, list, str]]: """Get detailed attributes of an image. :param image_name: Image name to inspect @@ -864,7 +861,7 @@ def delete_network(self, network_name: str) -> None: """ @abstractmethod - def inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: + def inspect_network(self, network_name: str) -> dict[str, Union[dict, str]]: """Get detailed attributes of an network. :return: Dict containing docker attributes as returned by the daemon @@ -875,8 +872,8 @@ def connect_container_to_network( self, network_name: str, container_name_or_id: str, - aliases: Optional[List] = None, - link_local_ips: List[str] = None, + aliases: Optional[list] = None, + link_local_ips: list[str] = None, ) -> None: """ Connects a container to a given network @@ -911,7 +908,7 @@ def get_container_ip(self, container_name_or_id: str) -> str: If container has multiple networks, it will return the IP of the first """ - def get_image_cmd(self, docker_image: str, pull: bool = True) -> List[str]: + def get_image_cmd(self, docker_image: str, pull: bool = True) -> list[str]: """Get the command for the given image :param docker_image: Docker image to inspect :param pull: Whether to pull if image is not present @@ -988,28 +985,28 @@ def create_container( image_name: str, *, name: Optional[str] = None, - entrypoint: Optional[Union[List[str], str]] = None, + entrypoint: Optional[Union[list[str], str]] = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[Union[VolumeMappings, List[SimpleVolumeBind]]] = None, + command: Optional[Union[list[str], str]] = None, + volumes: Optional[Union[VolumeMappings, list[SimpleVolumeBind]]] = None, ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, + exposed_ports: Optional[list[str]] = None, + env_vars: Optional[dict[str, str]] = None, user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, + cap_add: Optional[list[str]] = None, + cap_drop: Optional[list[str]] = None, + security_opt: Optional[list[str]] = None, network: Optional[str] = None, - dns: Optional[Union[str, List[str]]] = None, + dns: Optional[Union[str, list[str]]] = None, additional_flags: Optional[str] = None, workdir: Optional[str] = None, privileged: Optional[bool] = None, - labels: Optional[Dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, platform: Optional[DockerPlatform] = None, - ulimits: Optional[List[Ulimit]] = None, + ulimits: Optional[list[Ulimit]] = None, init: Optional[bool] = None, log_config: Optional[LogConfig] = None, ) -> str: @@ -1030,26 +1027,26 @@ def run_container( interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[Union[VolumeMappings, List[SimpleVolumeBind]]] = None, + command: Optional[Union[list[str], str]] = None, + volumes: Optional[Union[VolumeMappings, list[SimpleVolumeBind]]] = None, ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, + exposed_ports: Optional[list[str]] = None, + env_vars: Optional[dict[str, str]] = None, user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, + cap_add: Optional[list[str]] = None, + cap_drop: Optional[list[str]] = None, + security_opt: Optional[list[str]] = None, network: Optional[str] = None, dns: Optional[str] = None, additional_flags: Optional[str] = None, workdir: Optional[str] = None, - labels: Optional[Dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, platform: Optional[DockerPlatform] = None, privileged: Optional[bool] = None, - ulimits: Optional[List[Ulimit]] = None, + ulimits: Optional[list[Ulimit]] = None, init: Optional[bool] = None, log_config: Optional[LogConfig] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: """Creates and runs a given docker container :return: A tuple (stdout, stderr) @@ -1057,7 +1054,7 @@ def run_container( def run_container_from_config( self, container_config: ContainerConfiguration - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: """Like ``run_container`` but uses the parameters from the configuration.""" return self.run_container( @@ -1093,14 +1090,14 @@ def run_container_from_config( def exec_in_container( self, container_name_or_id: str, - command: Union[List[str], str], + command: Union[list[str], str], interactive: bool = False, detach: bool = False, - env_vars: Optional[Dict[str, Optional[str]]] = None, + env_vars: Optional[dict[str, Optional[str]]] = None, stdin: Optional[bytes] = None, user: Optional[str] = None, workdir: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: """Execute a given command in a container :return: A tuple (stdout, stderr) @@ -1114,7 +1111,7 @@ def start_container( interactive: bool = False, attach: bool = False, flags: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: """Start a given, already created container :return: A tuple (stdout, stderr) if attach or interactive is set, otherwise a tuple (b"container_name_or_id", b"") @@ -1147,7 +1144,7 @@ def format_env_vars(key: str, value: Optional[str]): return f"{key}={value}" @classmethod - def create_env_vars_file_flag(cls, env_vars: Dict) -> Tuple[List[str], Optional[str]]: + def create_env_vars_file_flag(cls, env_vars: dict) -> tuple[list[str], Optional[str]]: if not env_vars: return [], None result = [] @@ -1185,14 +1182,14 @@ def mountable_tmp_file(): return f @staticmethod - def append_without_latest(image_names: List[str]): + def append_without_latest(image_names: list[str]): suffix = ":latest" for image in list(image_names): if image.endswith(suffix): image_names.append(image[: -len(suffix)]) @staticmethod - def strip_wellknown_repo_prefixes(image_names: List[str]) -> List[str]: + def strip_wellknown_repo_prefixes(image_names: list[str]) -> list[str]: """ Remove well-known repo prefixes like `localhost/` or `docker.io/library/` from the list of given image names. This is mostly to ensure compatibility of our Docker client with Podman API responses. @@ -1238,7 +1235,7 @@ def untar_to_path(tardata, target_path): LOG.debug("File to copy empty, ignoring...") @staticmethod - def _read_docker_cli_env_file(env_file: str) -> Dict[str, str]: + def _read_docker_cli_env_file(env_file: str) -> dict[str, str]: """ Read an environment file in docker CLI format, specified here: https://docs.docker.com/reference/cli/docker/container/run/#env @@ -1247,7 +1244,7 @@ def _read_docker_cli_env_file(env_file: str) -> Dict[str, str]: """ env_vars = {} try: - with open(env_file, mode="rt") as f: + with open(env_file) as f: env_file_lines = f.readlines() except FileNotFoundError as e: LOG.error( @@ -1280,16 +1277,16 @@ def _read_docker_cli_env_file(env_file: str) -> Dict[str, str]: @staticmethod def parse_additional_flags( additional_flags: str, - env_vars: Optional[Dict[str, str]] = None, - labels: Optional[Dict[str, str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, + env_vars: Optional[dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, + volumes: Optional[list[SimpleVolumeBind]] = None, network: Optional[str] = None, platform: Optional[DockerPlatform] = None, ports: Optional[PortMappings] = None, privileged: Optional[bool] = None, user: Optional[str] = None, - ulimits: Optional[List[Ulimit]] = None, - dns: Optional[Union[str, List[str]]] = None, + ulimits: Optional[list[Ulimit]] = None, + dns: Optional[Union[str, list[str]]] = None, ) -> DockerRunFlags: """Parses additional CLI-formatted Docker flags, which could overwrite provided defaults. :param additional_flags: String which contains the flag definitions inspired by the Docker CLI reference: @@ -1510,8 +1507,8 @@ def parse_additional_flags( @staticmethod def convert_mount_list_to_dict( - volumes: Union[List[SimpleVolumeBind], VolumeMappings], - ) -> Dict[str, Dict[str, str]]: + volumes: Union[list[SimpleVolumeBind], VolumeMappings], + ) -> dict[str, dict[str, str]]: """Converts a List of (host_path, container_path) tuples to a Dict suitable as volume argument for docker sdk""" def _map_to_dict(paths: SimpleVolumeBind | BindMount | VolumeDirMount): diff --git a/localstack-core/localstack/utils/container_utils/docker_cmd_client.py b/localstack-core/localstack/utils/container_utils/docker_cmd_client.py index ebb5dc7a10dd0..2de4e059c1b42 100644 --- a/localstack-core/localstack/utils/container_utils/docker_cmd_client.py +++ b/localstack-core/localstack/utils/container_utils/docker_cmd_client.py @@ -6,7 +6,7 @@ import re import shlex import subprocess -from typing import Callable, Dict, List, Optional, Tuple, Union +from typing import Callable, Optional, Union from localstack import config from localstack.utils.collections import ensure_list @@ -98,7 +98,7 @@ class CmdDockerClient(ContainerClient): default_run_outfile: Optional[str] = None - def _docker_cmd(self) -> List[str]: + def _docker_cmd(self) -> list[str]: """ Get the configured, tested Docker CMD. :return: string to be used for running Docker commands @@ -281,7 +281,7 @@ def remove_container(self, container_name: str, force=True, check_existence=Fals "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr ) from e - def list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: + def list_containers(self, filter: Union[list[str], str, None] = None, all=True) -> list[dict]: filter = [filter] if isinstance(filter, str) else filter cmd = self._docker_cmd() cmd.append("ps") @@ -489,7 +489,7 @@ def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: return CancellableProcessStream(process) - def _inspect_object(self, object_name_or_id: str) -> Dict[str, Union[dict, list, str]]: + def _inspect_object(self, object_name_or_id: str) -> dict[str, Union[dict, list, str]]: cmd = self._docker_cmd() cmd += ["inspect", "--format", "{{json .}}", object_name_or_id] try: @@ -516,7 +516,7 @@ def _inspect_object(self, object_name_or_id: str) -> Dict[str, Union[dict, list, ) return object_data - def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: + def inspect_container(self, container_name_or_id: str) -> dict[str, Union[dict, str]]: try: return self._inspect_object(container_name_or_id) except NoSuchObject as e: @@ -527,7 +527,7 @@ def inspect_image( image_name: str, pull: bool = True, strip_wellknown_repo_prefixes: bool = True, - ) -> Dict[str, Union[dict, list, str]]: + ) -> dict[str, Union[dict, list, str]]: image_name = self.registry_resolver_strategy.resolve(image_name) try: result = self._inspect_object(image_name) @@ -569,7 +569,7 @@ def delete_network(self, network_name: str) -> None: "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr ) from e - def inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: + def inspect_network(self, network_name: str) -> dict[str, Union[dict, str]]: try: return self._inspect_object(network_name) except NoSuchObject as e: @@ -579,8 +579,8 @@ def connect_container_to_network( self, network_name: str, container_name_or_id: str, - aliases: Optional[List] = None, - link_local_ips: List[str] = None, + aliases: Optional[list] = None, + link_local_ips: list[str] = None, ) -> None: LOG.debug( "Connecting container '%s' to network '%s' with aliases '%s'", @@ -657,7 +657,7 @@ def login(self, username: str, password: str, registry: Optional[str] = None) -> "Docker process returned with errorcode %s" % e.returncode, e.stdout, e.stderr ) from e - @functools.lru_cache(maxsize=None) + @functools.cache def has_docker(self) -> bool: try: # do not use self._docker_cmd here (would result in a loop) @@ -685,7 +685,7 @@ def create_container(self, image_name: str, **kwargs) -> str: finally: Util.rm_env_vars_file(env_file) - def run_container(self, image_name: str, stdin=None, **kwargs) -> Tuple[bytes, bytes]: + def run_container(self, image_name: str, stdin=None, **kwargs) -> tuple[bytes, bytes]: image_name = self.registry_resolver_strategy.resolve(image_name) cmd, env_file = self._build_run_create_cmd("run", image_name, **kwargs) LOG.debug("Run container with cmd: %s", cmd) @@ -701,14 +701,14 @@ def run_container(self, image_name: str, stdin=None, **kwargs) -> Tuple[bytes, b def exec_in_container( self, container_name_or_id: str, - command: Union[List[str], str], + command: Union[list[str], str], interactive=False, detach=False, - env_vars: Optional[Dict[str, Optional[str]]] = None, + env_vars: Optional[dict[str, Optional[str]]] = None, stdin: Optional[bytes] = None, user: Optional[str] = None, workdir: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: env_file = None cmd = self._docker_cmd() cmd.append("exec") @@ -724,7 +724,7 @@ def exec_in_container( env_flag, env_file = Util.create_env_vars_file_flag(env_vars) cmd += env_flag cmd.append(container_name_or_id) - cmd += command if isinstance(command, List) else [command] + cmd += command if isinstance(command, list) else [command] LOG.debug("Execute command in container: %s", cmd) try: return self._run_async_cmd(cmd, stdin, container_name_or_id) @@ -738,7 +738,7 @@ def start_container( interactive: bool = False, attach: bool = False, flags: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: cmd = self._docker_cmd() + ["start"] if flags: cmd.append(flags) @@ -756,8 +756,8 @@ def attach_to_container(self, container_name_or_id: str): return self._run_async_cmd(cmd, stdin=None, container_name=container_name_or_id) def _run_async_cmd( - self, cmd: List[str], stdin: bytes, container_name: str, image_name=None - ) -> Tuple[bytes, bytes]: + self, cmd: list[str], stdin: bytes, container_name: str, image_name=None + ) -> tuple[bytes, bytes]: kwargs = { "inherit_env": True, "asynchronous": True, @@ -796,31 +796,31 @@ def _build_run_create_cmd( image_name: str, *, name: Optional[str] = None, - entrypoint: Optional[Union[List[str], str]] = None, + entrypoint: Optional[Union[list[str], str]] = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, + command: Optional[Union[list[str], str]] = None, + volumes: Optional[list[SimpleVolumeBind]] = None, ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, + exposed_ports: Optional[list[str]] = None, + env_vars: Optional[dict[str, str]] = None, user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, + cap_add: Optional[list[str]] = None, + cap_drop: Optional[list[str]] = None, + security_opt: Optional[list[str]] = None, network: Optional[str] = None, - dns: Optional[Union[str, List[str]]] = None, + dns: Optional[Union[str, list[str]]] = None, additional_flags: Optional[str] = None, workdir: Optional[str] = None, privileged: Optional[bool] = None, - labels: Optional[Dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, platform: Optional[DockerPlatform] = None, - ulimits: Optional[List[Ulimit]] = None, + ulimits: Optional[list[Ulimit]] = None, init: Optional[bool] = None, log_config: Optional[LogConfig] = None, - ) -> Tuple[List[str], str]: + ) -> tuple[list[str], str]: env_file = None cmd = self._docker_cmd() + [action] if remove: @@ -888,7 +888,7 @@ def _build_run_create_cmd( cmd += shlex.split(additional_flags) cmd.append(image_name) if command: - cmd += command if isinstance(command, List) else [command] + cmd += command if isinstance(command, list) else [command] return cmd, env_file @staticmethod @@ -922,13 +922,13 @@ def _check_and_raise_no_such_container_error( if any(msg.lower() in process_stdout_lower for msg in error_messages): raise NoSuchContainer(container_name_or_id, stdout=error.stdout, stderr=error.stderr) - def _transform_container_labels(self, labels: Union[str, Dict[str, str]]) -> Dict[str, str]: + def _transform_container_labels(self, labels: Union[str, dict[str, str]]) -> dict[str, str]: """ Transforms the container labels returned by the docker command from the key-value pair format to a dict :param labels: Input string, comma separated key value pairs. Example: key1=value1,key2=value2 :return: Dict representation of the passed values, example: {"key1": "value1", "key2": "value2"} """ - if isinstance(labels, Dict): + if isinstance(labels, dict): return labels labels = labels.split(",") diff --git a/localstack-core/localstack/utils/container_utils/docker_sdk_client.py b/localstack-core/localstack/utils/container_utils/docker_sdk_client.py index a01761d20d44f..21f3f4c123cf5 100644 --- a/localstack-core/localstack/utils/container_utils/docker_sdk_client.py +++ b/localstack-core/localstack/utils/container_utils/docker_sdk_client.py @@ -6,9 +6,9 @@ import re import socket import threading -from functools import lru_cache +from functools import cache from time import sleep -from typing import Callable, Dict, List, Optional, Tuple, Union, cast +from typing import Callable, Optional, Union, cast from urllib.parse import quote import docker @@ -278,7 +278,7 @@ def remove_container(self, container_name: str, force=True, check_existence=Fals except APIError as e: raise ContainerException() from e - def list_containers(self, filter: Union[List[str], str, None] = None, all=True) -> List[dict]: + def list_containers(self, filter: Union[list[str], str, None] = None, all=True) -> list[dict]: if filter: filter = [filter] if isinstance(filter, str) else filter filter = dict([f.split("=", 1) for f in filter]) @@ -344,7 +344,7 @@ def pull_image( # some path in the docker image string indicates a custom repository docker_image = self.registry_resolver_strategy.resolve(docker_image) - kwargs: Dict[str, Union[str, bool]] = {"platform": platform} + kwargs: dict[str, Union[str, bool]] = {"platform": platform} try: if log_handler: # Use a lower-level API, as the 'stream' argument is not available in the higher-level `pull`-API @@ -466,7 +466,7 @@ def stream_container_logs(self, container_name_or_id: str) -> CancellableStream: except APIError as e: raise ContainerException() from e - def inspect_container(self, container_name_or_id: str) -> Dict[str, Union[Dict, str]]: + def inspect_container(self, container_name_or_id: str) -> dict[str, Union[dict, str]]: try: return self.client().containers.get(container_name_or_id).attrs except NotFound: @@ -479,7 +479,7 @@ def inspect_image( image_name: str, pull: bool = True, strip_wellknown_repo_prefixes: bool = True, - ) -> Dict[str, Union[dict, list, str]]: + ) -> dict[str, Union[dict, list, str]]: image_name = self.registry_resolver_strategy.resolve(image_name) try: result = self.client().images.get(image_name).attrs @@ -513,7 +513,7 @@ def delete_network(self, network_name: str) -> None: except APIError as e: raise ContainerException() from e - def inspect_network(self, network_name: str) -> Dict[str, Union[Dict, str]]: + def inspect_network(self, network_name: str) -> dict[str, Union[dict, str]]: try: return self.client().networks.get(network_name).attrs except NotFound: @@ -525,8 +525,8 @@ def connect_container_to_network( self, network_name: str, container_name_or_id: str, - aliases: Optional[List] = None, - link_local_ips: List[str] = None, + aliases: Optional[list] = None, + link_local_ips: list[str] = None, ) -> None: LOG.debug( "Connecting container '%s' to network '%s' with aliases '%s'", @@ -574,7 +574,7 @@ def get_container_ip(self, container_name_or_id: str) -> str: LOG.info("Container has more than one assigned network. Picking the first one...") return networks[network_names[0]]["IPAddress"] - @lru_cache(maxsize=None) + @cache def has_docker(self) -> bool: try: if not self.docker_client: @@ -620,7 +620,7 @@ def start_container( interactive: bool = False, attach: bool = False, flags: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: LOG.debug("Starting container %s", container_name_or_id) try: container = self.client().containers.get(container_name_or_id) @@ -702,28 +702,28 @@ def create_container( image_name: str, *, name: Optional[str] = None, - entrypoint: Optional[Union[List[str], str]] = None, + entrypoint: Optional[Union[list[str], str]] = None, remove: bool = False, interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, + command: Optional[Union[list[str], str]] = None, + volumes: Optional[list[SimpleVolumeBind]] = None, ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, + exposed_ports: Optional[list[str]] = None, + env_vars: Optional[dict[str, str]] = None, user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, + cap_add: Optional[list[str]] = None, + cap_drop: Optional[list[str]] = None, + security_opt: Optional[list[str]] = None, network: Optional[str] = None, - dns: Optional[Union[str, List[str]]] = None, + dns: Optional[Union[str, list[str]]] = None, additional_flags: Optional[str] = None, workdir: Optional[str] = None, privileged: Optional[bool] = None, - labels: Optional[Dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, platform: Optional[DockerPlatform] = None, - ulimits: Optional[List[Ulimit]] = None, + ulimits: Optional[list[Ulimit]] = None, init: Optional[bool] = None, log_config: Optional[LogConfig] = None, ) -> str: @@ -838,26 +838,26 @@ def run_container( interactive: bool = False, tty: bool = False, detach: bool = False, - command: Optional[Union[List[str], str]] = None, - volumes: Optional[List[SimpleVolumeBind]] = None, + command: Optional[Union[list[str], str]] = None, + volumes: Optional[list[SimpleVolumeBind]] = None, ports: Optional[PortMappings] = None, - exposed_ports: Optional[List[str]] = None, - env_vars: Optional[Dict[str, str]] = None, + exposed_ports: Optional[list[str]] = None, + env_vars: Optional[dict[str, str]] = None, user: Optional[str] = None, - cap_add: Optional[List[str]] = None, - cap_drop: Optional[List[str]] = None, - security_opt: Optional[List[str]] = None, + cap_add: Optional[list[str]] = None, + cap_drop: Optional[list[str]] = None, + security_opt: Optional[list[str]] = None, network: Optional[str] = None, dns: Optional[str] = None, additional_flags: Optional[str] = None, workdir: Optional[str] = None, - labels: Optional[Dict[str, str]] = None, + labels: Optional[dict[str, str]] = None, platform: Optional[DockerPlatform] = None, privileged: Optional[bool] = None, - ulimits: Optional[List[Ulimit]] = None, + ulimits: Optional[list[Ulimit]] = None, init: Optional[bool] = None, log_config: Optional[LogConfig] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: LOG.debug("Running container with image: %s", image_name) container = None try: @@ -903,14 +903,14 @@ def run_container( def exec_in_container( self, container_name_or_id: str, - command: Union[List[str], str], + command: Union[list[str], str], interactive=False, detach=False, - env_vars: Optional[Dict[str, Optional[str]]] = None, + env_vars: Optional[dict[str, Optional[str]]] = None, stdin: Optional[bytes] = None, user: Optional[str] = None, workdir: Optional[str] = None, - ) -> Tuple[bytes, bytes]: + ) -> tuple[bytes, bytes]: LOG.debug("Executing command in container %s: %s", container_name_or_id, command) try: container: Container = self.client().containers.get(container_name_or_id) diff --git a/localstack-core/localstack/utils/crypto.py b/localstack-core/localstack/utils/crypto.py index bd7150d96b871..88fd2ead0ac9c 100644 --- a/localstack-core/localstack/utils/crypto.py +++ b/localstack-core/localstack/utils/crypto.py @@ -3,7 +3,6 @@ import os import re import threading -from typing import Tuple from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes @@ -105,7 +104,7 @@ def store_cert_key_files(base_filename): cert.set_pubkey(k) alt_names = ( f"DNS:localhost,DNS:test.localhost.atlassian.io,DNS:localhost.localstack.cloud,DNS:{host_definition.host}IP:127.0.0.1" - ).encode("utf8") + ).encode() cert.add_extensions( [ crypto.X509Extension(b"subjectAltName", False, alt_names), @@ -165,7 +164,7 @@ def unpad(s: bytes) -> bytes: return s[0 : -s[-1]] -def encrypt(key: bytes, message: bytes, iv: bytes = None, aad: bytes = None) -> Tuple[bytes, bytes]: +def encrypt(key: bytes, message: bytes, iv: bytes = None, aad: bytes = None) -> tuple[bytes, bytes]: iv = iv or b"0" * BLOCK_SIZE cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend()) encryptor = cipher.encryptor() diff --git a/localstack-core/localstack/utils/diagnose.py b/localstack-core/localstack/utils/diagnose.py index 36b0b079631f9..e75dfd2163daa 100644 --- a/localstack-core/localstack/utils/diagnose.py +++ b/localstack-core/localstack/utils/diagnose.py @@ -3,7 +3,7 @@ import inspect import os import socket -from typing import Dict, List, Optional, Union +from typing import Optional, Union from localstack import config from localstack.constants import DEFAULT_VOLUME_DIR @@ -41,7 +41,7 @@ INSPECT_DIRECTORIES = [DEFAULT_VOLUME_DIR, "/tmp"] -def get_localstack_logs() -> Dict: +def get_localstack_logs() -> dict: try: result = DOCKER_CLIENT.get_container_logs(get_main_container_name()) except Exception as e: @@ -50,7 +50,7 @@ def get_localstack_logs() -> Dict: return {"docker": result} -def get_localstack_config() -> Dict: +def get_localstack_config() -> dict: result = {} for k, v in inspect.getmembers(config): if k in EXCLUDE_CONFIG_KEYS: @@ -77,14 +77,14 @@ def get_localstack_config() -> Dict: return result -def inspect_main_container() -> Union[str, Dict]: +def inspect_main_container() -> Union[str, dict]: try: return DOCKER_CLIENT.inspect_container(get_main_container_name()) except Exception as e: return f"inspect failed: {e}" -def get_localstack_version() -> Dict[str, Optional[str]]: +def get_localstack_version() -> dict[str, Optional[str]]: return { "build-date": os.environ.get("LOCALSTACK_BUILD_DATE"), "build-git-hash": os.environ.get("LOCALSTACK_BUILD_GIT_HASH"), @@ -92,7 +92,7 @@ def get_localstack_version() -> Dict[str, Optional[str]]: } -def resolve_endpoints() -> Dict[str, str]: +def resolve_endpoints() -> dict[str, str]: result = {} for endpoint in ENDPOINT_RESOLVE_LIST: try: @@ -103,7 +103,7 @@ def resolve_endpoints() -> Dict[str, str]: return result -def get_important_image_hashes() -> Dict[str, str]: +def get_important_image_hashes() -> dict[str, str]: result = {} for image in DIAGNOSE_IMAGES: try: @@ -116,17 +116,17 @@ def get_important_image_hashes() -> Dict[str, str]: return result -def get_service_stats() -> Dict[str, str]: +def get_service_stats() -> dict[str, str]: from localstack.services.plugins import SERVICE_PLUGINS return {service: state.value for service, state in SERVICE_PLUGINS.get_states().items()} -def get_file_tree() -> Dict[str, List[str]]: +def get_file_tree() -> dict[str, list[str]]: return {d: traverse_file_tree(d) for d in INSPECT_DIRECTORIES} -def traverse_file_tree(root: str) -> List[str]: +def traverse_file_tree(root: str) -> list[str]: try: result = [] if config.in_docker(): @@ -137,7 +137,7 @@ def traverse_file_tree(root: str) -> List[str]: return ["traversing files failed %s" % e] -def get_docker_image_details() -> Dict[str, str]: +def get_docker_image_details() -> dict[str, str]: try: image = DOCKER_CLIENT.inspect_container(get_main_container_name())["Config"]["Image"] except ContainerException: diff --git a/localstack-core/localstack/utils/docker_utils.py b/localstack-core/localstack/utils/docker_utils.py index 9ff5f57134ca6..977521c0ff59f 100644 --- a/localstack-core/localstack/utils/docker_utils.py +++ b/localstack-core/localstack/utils/docker_utils.py @@ -2,7 +2,7 @@ import logging import platform import random -from typing import List, Optional, Union +from typing import Optional, Union from localstack import config from localstack.constants import DEFAULT_VOLUME_DIR, DOCKER_IMAGE_NAME @@ -74,11 +74,11 @@ def get_current_container_id() -> str: return container_id -def inspect_current_container_mounts() -> List[VolumeInfo]: +def inspect_current_container_mounts() -> list[VolumeInfo]: return DOCKER_CLIENT.inspect_container_volumes(get_current_container_id()) -@functools.lru_cache() +@functools.lru_cache def get_default_volume_dir_mount() -> Optional[VolumeInfo]: """ Returns the volume information of LocalStack's DEFAULT_VOLUME_DIR (/var/lib/localstack), if mounted, @@ -132,7 +132,7 @@ def get_host_path_for_path_in_docker(path): def container_ports_can_be_bound( - ports: Union[IntOrPort, List[IntOrPort]], + ports: Union[IntOrPort, list[IntOrPort]], address: Optional[str] = None, ) -> bool: """Determine whether a given list of ports can be bound by Docker containers diff --git a/localstack-core/localstack/utils/files.py b/localstack-core/localstack/utils/files.py index 97699f151e3a2..3fbb271dd5d59 100644 --- a/localstack-core/localstack/utils/files.py +++ b/localstack-core/localstack/utils/files.py @@ -6,13 +6,12 @@ import stat import tempfile from pathlib import Path -from typing import Dict LOG = logging.getLogger(__name__) TMP_FILES = [] -def parse_config_file(file_or_str: str, single_section: bool = True) -> Dict: +def parse_config_file(file_or_str: str, single_section: bool = True) -> dict: """Parse the given properties config file/string and return a dict of section->key->value. If the config contains a single section, and 'single_section' is True, returns""" diff --git a/localstack-core/localstack/utils/functions.py b/localstack-core/localstack/utils/functions.py index 0640a84fea2a0..63706cf9d44c0 100644 --- a/localstack-core/localstack/utils/functions.py +++ b/localstack-core/localstack/utils/functions.py @@ -3,7 +3,7 @@ import functools import inspect import logging -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any, Callable, Optional LOG = logging.getLogger(__name__) @@ -19,7 +19,7 @@ def run_safe(_python_lambda, *args, _default=None, **kwargs): def call_safe( - func: Callable, args: Tuple = None, kwargs: Dict = None, exception_message: str = None + func: Callable, args: tuple = None, kwargs: dict = None, exception_message: str = None ) -> Optional[Any]: """ Call the given function with the given arguments, and if it fails, log the given exception_message. diff --git a/localstack-core/localstack/utils/http.py b/localstack-core/localstack/utils/http.py index 3e012fd71dad3..6d6d3704cee8d 100644 --- a/localstack-core/localstack/utils/http.py +++ b/localstack-core/localstack/utils/http.py @@ -2,7 +2,7 @@ import math import os import re -from typing import Dict, Optional, Union +from typing import Optional, Union from urllib.parse import parse_qs, parse_qsl, urlencode, urlparse, urlunparse import requests @@ -54,7 +54,7 @@ def create_chunked_data(data, chunk_size: int = 80): return ret -def canonicalize_headers(headers: Union[Dict, CaseInsensitiveDict]) -> Dict: +def canonicalize_headers(headers: Union[dict, CaseInsensitiveDict]) -> dict: if not headers: return headers @@ -76,7 +76,7 @@ def add_path_parameters_to_url(uri: str, path_params: list): return urlunparse(url._replace(path=new_path)) -def add_query_params_to_url(uri: str, query_params: Dict) -> str: +def add_query_params_to_url(uri: str, query_params: dict) -> str: """ Add query parameters to the uri. :param uri: the base uri it can contains path arguments and query parameters @@ -103,7 +103,7 @@ def add_query_params_to_url(uri: str, query_params: Dict) -> str: def make_http_request( - url: str, data: Union[bytes, str] = None, headers: Dict[str, str] = None, method: str = "GET" + url: str, data: Union[bytes, str] = None, headers: dict[str, str] = None, method: str = "GET" ) -> Response: return requests.request( url=url, method=method, headers=headers, data=data, auth=NetrcBypassAuth(), verify=False @@ -141,7 +141,7 @@ def _wrapper(*args, **kwargs): safe_requests = _RequestsSafe() -def parse_request_data(method: str, path: str, data=None, headers=None) -> Dict: +def parse_request_data(method: str, path: str, data=None, headers=None) -> dict: """Extract request data either from query string as well as request body (e.g., for POST).""" result = {} headers = headers or {} @@ -165,7 +165,7 @@ def parse_request_data(method: str, path: str, data=None, headers=None) -> Dict: return result -def get_proxies() -> Dict[str, str]: +def get_proxies() -> dict[str, str]: proxy_map = {} if config.OUTBOUND_HTTP_PROXY: proxy_map["http"] = config.OUTBOUND_HTTP_PROXY diff --git a/localstack-core/localstack/utils/iputils.py b/localstack-core/localstack/utils/iputils.py index b28ea2215c3c5..4e738c4367b2a 100644 --- a/localstack-core/localstack/utils/iputils.py +++ b/localstack-core/localstack/utils/iputils.py @@ -6,7 +6,8 @@ import ipaddress import json import subprocess as sp -from typing import Any, Generator, TypedDict +from collections.abc import Generator +from typing import Any, TypedDict from cachetools import TTLCache, cached diff --git a/localstack-core/localstack/utils/json.py b/localstack-core/localstack/utils/json.py index e2edcf3b4df35..5a51c8f2f0734 100644 --- a/localstack-core/localstack/utils/json.py +++ b/localstack-core/localstack/utils/json.py @@ -78,7 +78,7 @@ def load(self): if os.path.isdir(self.path): raise IsADirectoryError - with open(self.path, "r") as fd: + with open(self.path) as fd: self.update(json.load(fd)) def save(self): diff --git a/localstack-core/localstack/utils/net.py b/localstack-core/localstack/utils/net.py index c01c34f900f82..8a6d858cb64ac 100644 --- a/localstack-core/localstack/utils/net.py +++ b/localstack-core/localstack/utils/net.py @@ -3,8 +3,9 @@ import re import socket import threading +from collections.abc import MutableMapping from contextlib import closing -from typing import Any, List, MutableMapping, NamedTuple, Optional, Union +from typing import Any, NamedTuple, Optional, Union from urllib.parse import urlparse import dns.resolver @@ -56,7 +57,7 @@ def is_port_open( port_or_url: Union[int, str], http_path: str = None, expect_success: bool = True, - protocols: Optional[Union[str, List[str]]] = None, + protocols: Optional[Union[str, list[str]]] = None, quiet: bool = True, ): from localstack.utils.http import safe_requests @@ -92,7 +93,7 @@ def is_port_open( answers = resolver.query("google.com", "A") assert len(answers) > 0 else: - sock.sendto(bytes(), (host, port)) + sock.sendto(b"", (host, port)) sock.recvfrom(1024) except Exception: if not quiet: @@ -190,7 +191,7 @@ def port_can_be_bound(port: IntOrPort, address: str = "") -> bool: return False -def get_free_udp_port(blocklist: List[int] = None) -> int: +def get_free_udp_port(blocklist: list[int] = None) -> int: blocklist = blocklist or [] for i in range(10): udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -202,7 +203,7 @@ def get_free_udp_port(blocklist: List[int] = None) -> int: raise Exception(f"Unable to determine free UDP port with blocklist {blocklist}") -def get_free_tcp_port(blocklist: List[int] = None) -> int: +def get_free_tcp_port(blocklist: list[int] = None) -> int: """ Tries to bind a socket to port 0 and returns the port that was assigned by the system. If the port is in the given ``blocklist``, or the port is marked as reserved in ``dynamic_port_range``, the procedure @@ -272,7 +273,7 @@ def resolve_hostname(hostname: str) -> Optional[str]: """Resolve the given hostname and return its IP address, or None if it cannot be resolved.""" try: return socket.gethostbyname(hostname) - except socket.error: + except OSError: return None @@ -280,7 +281,7 @@ def is_ip_address(addr: str) -> bool: try: socket.inet_aton(addr) return True - except socket.error: + except OSError: return False @@ -468,9 +469,9 @@ def get_docker_host_from_container() -> str: if config.is_in_docker: try: result = socket.gethostbyname("host.docker.internal") - except socket.error: + except OSError: result = socket.gethostbyname("host.containers.internal") - except socket.error: + except OSError: # TODO if neither host resolves, we might be in linux. We could just use the default gateway then pass return result diff --git a/localstack-core/localstack/utils/objects.py b/localstack-core/localstack/utils/objects.py index 9e5f5ba283e15..46d0d0abd73e6 100644 --- a/localstack-core/localstack/utils/objects.py +++ b/localstack-core/localstack/utils/objects.py @@ -1,12 +1,12 @@ import functools import re import threading -from typing import Any, Callable, Dict, Generic, List, Optional, Set, Type, TypeVar, Union +from typing import Any, Callable, Generic, Optional, TypeVar, Union from .collections import ensure_list from .strings import first_char_to_lower, first_char_to_upper -ComplexType = Union[List, Dict, object] +ComplexType = Union[list, dict, object] _T = TypeVar("_T") @@ -83,7 +83,7 @@ class SubtypesInstanceManager: """Simple instance manager base class that scans the subclasses of a base type for concrete named implementations, and lazily creates and returns (singleton) instances on demand.""" - _instances: Dict[str, "SubtypesInstanceManager"] + _instances: dict[str, "SubtypesInstanceManager"] @classmethod def get(cls, subtype_name: str, raise_if_missing: bool = True): @@ -104,7 +104,7 @@ def get(cls, subtype_name: str, raise_if_missing: bool = True): return instance @classmethod - def instances(cls) -> Dict[str, "SubtypesInstanceManager"]: + def instances(cls) -> dict[str, "SubtypesInstanceManager"]: base_type = cls.get_base_type() if not hasattr(base_type, "_instances"): base_type._instances = {} @@ -116,13 +116,13 @@ def impl_name() -> str: raise NotImplementedError @classmethod - def get_base_type(cls) -> Type: + def get_base_type(cls) -> type: """Get the base class for which instances are being managed - can be customized by subtypes.""" return cls # this requires that all subclasses have been imported before(!) -def get_all_subclasses(clazz: Type) -> Set[Type]: +def get_all_subclasses(clazz: type) -> set[type]: """Recursively get all subclasses of the given class.""" result = set() subs = clazz.__subclasses__() @@ -132,7 +132,7 @@ def get_all_subclasses(clazz: Type) -> Set[Type]: return result -def fully_qualified_class_name(klass: Type) -> str: +def fully_qualified_class_name(klass: type) -> str: return f"{klass.__module__}.{klass.__name__}" @@ -156,7 +156,7 @@ def recurse_object(obj: ComplexType, func: Callable, path: str = "") -> ComplexT def keys_to( - obj: ComplexType, op: Callable[[str], str], skip_children_of: List[str] = None + obj: ComplexType, op: Callable[[str], str], skip_children_of: list[str] = None ) -> ComplexType: """Recursively changes all dict keys to apply op. Skip children of any elements whose names are contained in skip_children_of (e.g., ['Tags'])""" @@ -175,11 +175,11 @@ def fix_keys(o, path="", **kwargs): return result -def keys_to_lower(obj: ComplexType, skip_children_of: List[str] = None) -> ComplexType: +def keys_to_lower(obj: ComplexType, skip_children_of: list[str] = None) -> ComplexType: return keys_to(obj, first_char_to_lower, skip_children_of) -def keys_to_upper(obj: ComplexType, skip_children_of: List[str] = None) -> ComplexType: +def keys_to_upper(obj: ComplexType, skip_children_of: list[str] = None) -> ComplexType: return keys_to(obj, first_char_to_upper, skip_children_of) diff --git a/localstack-core/localstack/utils/patch.py b/localstack-core/localstack/utils/patch.py index 2fa54e3cf2a39..90497d852960f 100644 --- a/localstack-core/localstack/utils/patch.py +++ b/localstack-core/localstack/utils/patch.py @@ -1,7 +1,7 @@ import functools import inspect import types -from typing import Any, Callable, List, Type +from typing import Any, Callable def get_defining_object(method): @@ -71,7 +71,7 @@ def proxy(*args, **kwargs): class Patch: - applied_patches: List["Patch"] = [] + applied_patches: list["Patch"] = [] """Bookkeeping for patches that are applied. You can use this to debug patches. For instance, you could write something like:: @@ -120,7 +120,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): return self @staticmethod - def extend_class(target: Type, fn: Callable): + def extend_class(target: type, fn: Callable): def _getattr(obj, name): if name != fn.__name__: raise AttributeError(f"`{target.__name__}` object has no attribute `{name}`") @@ -164,9 +164,9 @@ def __str__(self): class Patches: - patches: List[Patch] + patches: list[Patch] - def __init__(self, patches: List[Patch] = None) -> None: + def __init__(self, patches: list[Patch] = None) -> None: super().__init__() self.patches = [] diff --git a/localstack-core/localstack/utils/platform.py b/localstack-core/localstack/utils/platform.py index e91e6f33a8483..ac06fd9d7865c 100644 --- a/localstack-core/localstack/utils/platform.py +++ b/localstack-core/localstack/utils/platform.py @@ -14,14 +14,14 @@ def is_windows() -> bool: return "windows" == platform.system().lower() -@lru_cache() +@lru_cache def is_debian() -> bool: from localstack.utils.files import load_file return "Debian" in load_file("/etc/issue", "") -@lru_cache() +@lru_cache def is_redhat() -> bool: from localstack.utils.files import load_file diff --git a/localstack-core/localstack/utils/run.py b/localstack-core/localstack/utils/run.py index 2c5aa0b07355e..6fac801a68400 100644 --- a/localstack-core/localstack/utils/run.py +++ b/localstack-core/localstack/utils/run.py @@ -9,7 +9,7 @@ import time from functools import lru_cache from queue import Queue -from typing import Any, AnyStr, Callable, Dict, List, Optional, Union +from typing import Any, AnyStr, Callable, Optional, Union from localstack import config @@ -23,13 +23,13 @@ def run( - cmd: Union[str, List[str]], + cmd: Union[str, list[str]], print_error=True, asynchronous=False, stdin=False, stderr=subprocess.STDOUT, outfile=None, - env_vars: Optional[Dict[AnyStr, AnyStr]] = None, + env_vars: Optional[dict[AnyStr, AnyStr]] = None, inherit_cwd=False, inherit_env=True, tty=False, @@ -151,7 +151,7 @@ def _worker(*_args): time.sleep(0.5) -def run_interactive(command: List[str]): +def run_interactive(command: list[str]): """ Run an interactive command in a subprocess. This blocks the current thread and attaches sys.stdin to the process. Copied from https://stackoverflow.com/a/43012138/804840 @@ -196,7 +196,7 @@ def is_root() -> bool: return get_os_user() == "root" -@lru_cache() +@lru_cache def get_os_user() -> str: # using getpass.getuser() seems to be reporting a different/invalid user in Docker/macOS return run("whoami").strip() @@ -211,10 +211,10 @@ class ShellCommandThread(FuncThread): def __init__( self, - cmd: Union[str, List[str]], + cmd: Union[str, list[str]], params: Any = None, outfile: Union[str, int] = None, - env_vars: Dict[str, str] = None, + env_vars: dict[str, str] = None, stdin: bool = False, auto_restart: bool = False, quiet: bool = True, diff --git a/localstack-core/localstack/utils/scheduler.py b/localstack-core/localstack/utils/scheduler.py index c295ef70ced7a..b23f42d8f93c6 100644 --- a/localstack-core/localstack/utils/scheduler.py +++ b/localstack-core/localstack/utils/scheduler.py @@ -1,8 +1,9 @@ import queue import threading import time +from collections.abc import Mapping from concurrent.futures import Executor -from typing import Any, Callable, List, Mapping, Optional, Tuple, Union +from typing import Any, Callable, Optional, Union class ScheduledTask: @@ -26,8 +27,8 @@ def __init__( self.period = period self.start = start self.on_error = on_error - self.args = args or tuple() - self.kwargs = kwargs or dict() + self.args = args or () + self.kwargs = kwargs or {} self.deadline = None self.error = None @@ -97,7 +98,7 @@ def schedule( fixed_rate: bool = True, start: Optional[float] = None, on_error: Callable[[Exception], None] = None, - args: Optional[Union[Tuple, List[Any]]] = None, + args: Optional[Union[tuple, list[Any]]] = None, kwargs: Optional[Mapping[str, Any]] = None, ) -> ScheduledTask: """ diff --git a/localstack-core/localstack/utils/strings.py b/localstack-core/localstack/utils/strings.py index aead8aaade907..71be5ece96190 100644 --- a/localstack-core/localstack/utils/strings.py +++ b/localstack-core/localstack/utils/strings.py @@ -7,7 +7,7 @@ import string import uuid import zlib -from typing import Dict, List, Union +from typing import Union from localstack.config import DEFAULT_ENCODING @@ -86,7 +86,7 @@ def canonicalize_bool_to_str(val: bool) -> str: return "true" if str(val).lower() == "true" else "false" -def convert_to_printable_chars(value: Union[List, Dict, str]) -> str: +def convert_to_printable_chars(value: Union[list, dict, str]) -> str: """Removes all unprintable characters from the given string.""" from localstack.utils.objects import recurse_object diff --git a/localstack-core/localstack/utils/tagging.py b/localstack-core/localstack/utils/tagging.py index f2ab05160fd2b..19adbdd3a4a85 100644 --- a/localstack-core/localstack/utils/tagging.py +++ b/localstack-core/localstack/utils/tagging.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional class TaggingService: @@ -21,7 +21,7 @@ def list_tags_for_resource(self, arn: str, root_name: Optional[str] = None): result.append({self.key_field: k, self.value_field: v}) return {root_name: result} - def tag_resource(self, arn: str, tags: List[Dict[str, str]]): + def tag_resource(self, arn: str, tags: list[dict[str, str]]): if not tags: return if arn not in self.tags: @@ -29,7 +29,7 @@ def tag_resource(self, arn: str, tags: List[Dict[str, str]]): for t in tags: self.tags[arn][t[self.key_field]] = t[self.value_field] - def untag_resource(self, arn: str, tag_names: List[str]): + def untag_resource(self, arn: str, tag_names: list[str]): tags = self.tags.get(arn, {}) for name in tag_names: tags.pop(name, None) diff --git a/localstack-core/localstack/utils/testutil.py b/localstack-core/localstack/utils/testutil.py index 2701cd7ce23a5..1da5cb9950e61 100644 --- a/localstack-core/localstack/utils/testutil.py +++ b/localstack-core/localstack/utils/testutil.py @@ -7,7 +7,7 @@ import shutil import tempfile import time -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Optional from localstack.aws.api.lambda_ import Runtime from localstack.aws.connect import connect_externally_to, connect_to @@ -71,7 +71,7 @@ def is_local_test_mode(): def create_lambda_archive( script: str, get_content: bool = False, - libs: List[str] = None, + libs: list[str] = None, runtime: str = None, file_name: str = None, exclude_func: Callable[[str], bool] = None, @@ -255,7 +255,7 @@ def create_lambda_function( "Role": role or LAMBDA_TEST_ROLE.format(account_id=TEST_AWS_ACCOUNT_ID), "Code": lambda_code, "Timeout": timeout or LAMBDA_TIMEOUT_SEC, - "Environment": dict(Variables=envvars), + "Environment": {"Variables": envvars}, "Tags": tags, } kwargs.update(additional_kwargs) @@ -425,7 +425,7 @@ def list_all_s3_objects(s3_client): return map_all_s3_objects(s3_client=s3_client).values() -def delete_all_s3_objects(s3_client, buckets: str | List[str]): +def delete_all_s3_objects(s3_client, buckets: str | list[str]): buckets = ensure_list(buckets) for bucket in buckets: keys = all_s3_object_keys(s3_client, bucket) @@ -444,15 +444,15 @@ def download_s3_object(s3_client, bucket, path): return result -def all_s3_object_keys(s3_client, bucket: str) -> List[str]: +def all_s3_object_keys(s3_client, bucket: str) -> list[str]: response = s3_client.list_objects_v2(Bucket=bucket) keys = [obj["Key"] for obj in response.get("Contents", [])] return keys def map_all_s3_objects( - s3_client, to_json: bool = True, buckets: str | List[str] = None -) -> Dict[str, Any]: + s3_client, to_json: bool = True, buckets: str | list[str] = None +) -> dict[str, Any]: result = {} buckets = ensure_list(buckets) if not buckets: @@ -499,7 +499,7 @@ def send_update_dynamodb_ttl_request(table_name, ttl_status): def send_dynamodb_request(path, action, request_body): headers = { "Host": "dynamodb.amazonaws.com", - "x-amz-target": "DynamoDB_20120810.{}".format(action), + "x-amz-target": f"DynamoDB_20120810.{action}", "Authorization": mock_aws_request_headers( "dynamodb", aws_access_key_id=TEST_AWS_ACCESS_KEY_ID, region_name=TEST_AWS_REGION_NAME )["Authorization"], @@ -509,7 +509,7 @@ def send_dynamodb_request(path, action, request_body): def get_lambda_log_group_name(function_name): - return "/aws/lambda/{}".format(function_name) + return f"/aws/lambda/{function_name}" # TODO: make logs_client mandatory @@ -537,7 +537,7 @@ def check_expected_lambda_log_events_length( return events -def list_all_log_events(log_group_name: str, logs_client=None) -> List[Dict]: +def list_all_log_events(log_group_name: str, logs_client=None) -> list[dict]: logs = logs_client or connect_to().logs return list_all_resources( lambda kwargs: logs.filter_log_events(logGroupName=log_group_name, **kwargs), diff --git a/localstack-core/localstack/utils/threads.py b/localstack-core/localstack/utils/threads.py index a981160177b34..403a872546a03 100644 --- a/localstack-core/localstack/utils/threads.py +++ b/localstack-core/localstack/utils/threads.py @@ -5,7 +5,7 @@ import traceback from concurrent.futures import Future from multiprocessing.dummy import Pool -from typing import Callable, List, Optional +from typing import Callable, Optional LOG = logging.getLogger(__name__) @@ -153,7 +153,7 @@ def cleanup_threads_and_processes(quiet=True): TMP_PROCESSES.clear() -def parallelize(func: Callable, arr: List, size: int = None): +def parallelize(func: Callable, arr: list, size: int = None): if not size: size = len(arr) if size <= 0: diff --git a/localstack-core/localstack/utils/xml.py b/localstack-core/localstack/utils/xml.py index 3e17cb57cc466..16e1c59af7ff1 100644 --- a/localstack-core/localstack/utils/xml.py +++ b/localstack-core/localstack/utils/xml.py @@ -9,7 +9,7 @@ def obj_to_xml(obj: Any) -> str: if isinstance(obj, list): return "".join([obj_to_xml(o) for o in obj]) if isinstance(obj, dict): - return "".join(["<{k}>{v}".format(k=k, v=obj_to_xml(v)) for (k, v) in obj.items()]) + return "".join([f"<{k}>{obj_to_xml(v)}" for (k, v) in obj.items()]) return str(obj) diff --git a/pyproject.toml b/pyproject.toml index 88d32dc65d889..2bdd88941d566 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,9 +54,9 @@ Issues = "https://github.com/localstack/localstack/issues" # minimal required to actually run localstack on the host for services natively implemented in python base-runtime = [ # pinned / updated by ASF update action - "boto3==1.39.14", + "boto3==1.40.6", # pinned / updated by ASF update action - "botocore==1.39.14", + "botocore==1.40.6", "awscrt>=0.13.14,!=0.27.1", "cbor2>=5.5.0", "dnspython>=1.16.0", @@ -79,7 +79,7 @@ base-runtime = [ runtime = [ "localstack-core[base-runtime]", # pinned / updated by ASF update action - "awscli==1.41.14", + "awscli==1.42.6", "airspeed-ext>=0.6.3", # version that has a built wheel "kclpy-ext>=3.0.0", @@ -200,10 +200,8 @@ exclude = [ "localstack-core/localstack/packages/**" = "py39" "localstack-core/localstack/config.py" = "py39" "localstack-core/localstack/constants.py" = "py39" -"localstack-core/localstack/utils/analytics/**" = "py39" -"localstack-core/localstack/utils/bootstrap.py" = "py39" -"localstack-core/localstack/utils/json.py" = "py39" - +"localstack-core/localstack/utils/**" = "py39" # imported by CLI tests +"localstack-core/localstack/testing/pytest/**" = "py39" # imported by CLI tests [tool.ruff.lint] ignore = [ @@ -215,16 +213,35 @@ ignore = [ "B024", # TODO x is an abstract base class, but it has no abstract methods "B027", # TODO `Server.do_shutdown` is an empty method in an abstract base class, but has no abstract decorator "B904", # TODO Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling - "C408", # TODO Unnecessary `list` call (rewrite as a literal) "C416", # TODO Unnecessary `set` comprehension "C901", # TODO function is too complex "E402", # TODO Module level import not at top of file "E501", # E501 Line too long - handled by black, see https://docs.astral.sh/ruff/faq/#is-ruff-compatible-with-black - "E721", # TODO Do not compare types, use `isinstance()` "T201", # TODO `print` found "T203", # TODO `pprint` found + "UP008",# TODO Use `super()` instead of `super(__class__, self)` + "UP028",# TODO Replace `yield` over `for` loop with `yield from` + "UP031",# TODO Use format specifiers instead of percent format + "UP036",# TODO Version block is outdated for minimum Python version + "UP038",# TODO Use `X | Y` in `isinstance` call instead of `(X, Y)` +] +select = ["B", "C", "E", "F", "I", "W", "T", "B9", "G", "UP"] +extend-safe-fixes = [ + "UP006", # unsafe-fix for py39 + "UP035", # unsafe-fix for py39 ] -select = ["B", "C", "E", "F", "I", "W", "T", "B9", "G"] + +# The rules below fill fix the code in a way that leaves multiple unused imports. +# Since F401 (removing unused imports) is currently an unsafe fix, these imports cannot be automatically removed. +# Therefore, we temporarily disable the rules below for __init__ files. +[tool.ruff.lint.per-file-ignores] +"localstack-core/localstack/**/__init__.py" = ["UP006", "UP007", "UP035", "UP045"] +"tests/aws/services/lambda_/functions/**" = ["UP"] # lambda tests parametrize the runtime + +[tool.ruff.lint.pyupgrade] +# Avoid PEP 604 rewrites (e.g. Union[str, int] -> str | int), even with the `from __future__ import annotations` +# import. It only affects py39 and can be removed after we drop it. +keep-runtime-typing = true [tool.coverage.run] relative_files = true diff --git a/requirements-base-runtime.txt b/requirements-base-runtime.txt index 8eeb89bb0a39b..e39de9f1f9184 100644 --- a/requirements-base-runtime.txt +++ b/requirements-base-runtime.txt @@ -9,32 +9,32 @@ attrs==25.3.0 # jsonschema # localstack-twisted # referencing -awscrt==0.27.4 +awscrt==0.27.5 # via localstack-core (pyproject.toml) -boto3==1.39.14 +boto3==1.40.6 # via localstack-core (pyproject.toml) -botocore==1.39.14 +botocore==1.40.6 # via # boto3 # localstack-core (pyproject.toml) # s3transfer -build==1.2.2.post1 +build==1.3.0 # via localstack-core (pyproject.toml) cachetools==6.1.0 # via localstack-core (pyproject.toml) cbor2==5.6.5 # via localstack-core (pyproject.toml) -certifi==2025.7.14 +certifi==2025.8.3 # via requests cffi==1.17.1 # via cryptography -charset-normalizer==3.4.2 +charset-normalizer==3.4.3 # via requests click==8.2.1 # via localstack-core (pyproject.toml) constantly==23.10.4 # via localstack-twisted -cryptography==45.0.5 +cryptography==45.0.6 # via # localstack-core (pyproject.toml) # pyopenssl @@ -98,7 +98,7 @@ lazy-object-proxy==1.11.0 # via openapi-spec-validator localstack-twisted==24.3.0 # via localstack-core (pyproject.toml) -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich markupsafe==3.0.2 # via werkzeug @@ -120,7 +120,7 @@ parse==1.20.2 # via openapi-core pathable==0.4.4 # via jsonschema-path -plux==1.12.1 +plux==1.13.0 # via localstack-core (pyproject.toml) priority==1.3.0 # via @@ -168,7 +168,7 @@ rich==14.1.0 # via localstack-core (pyproject.toml) rolo==0.7.6 # via localstack-core (pyproject.toml) -rpds-py==0.26.0 +rpds-py==0.27.0 # via # jsonschema # referencing diff --git a/requirements-basic.txt b/requirements-basic.txt index 8ffc71ee66cb7..6b76470fab394 100644 --- a/requirements-basic.txt +++ b/requirements-basic.txt @@ -4,19 +4,19 @@ # # pip-compile --output-file=requirements-basic.txt --strip-extras --unsafe-package=distribute --unsafe-package=localstack-core --unsafe-package=pip --unsafe-package=setuptools pyproject.toml # -build==1.2.2.post1 +build==1.3.0 # via localstack-core (pyproject.toml) cachetools==6.1.0 # via localstack-core (pyproject.toml) -certifi==2025.7.14 +certifi==2025.8.3 # via requests cffi==1.17.1 # via cryptography -charset-normalizer==3.4.2 +charset-normalizer==3.4.3 # via requests click==8.2.1 # via localstack-core (pyproject.toml) -cryptography==45.0.5 +cryptography==45.0.6 # via localstack-core (pyproject.toml) dill==0.3.6 # via localstack-core (pyproject.toml) @@ -28,13 +28,13 @@ idna==3.10 # via requests jsonpickle==4.1.1 # via localstack-core (pyproject.toml) -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich mdurl==0.1.2 # via markdown-it-py packaging==25.0 # via build -plux==1.12.1 +plux==1.13.0 # via localstack-core (pyproject.toml) psutil==7.0.0 # via localstack-core (pyproject.toml) diff --git a/requirements-dev.txt b/requirements-dev.txt index 58633eccbd375..65d773de52ad2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,7 +12,7 @@ antlr4-python3-runtime==4.13.2 # via # localstack-core # moto-ext -anyio==4.9.0 +anyio==4.10.0 # via httpx apispec==6.8.2 # via localstack-core @@ -29,9 +29,9 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==45.2.0 +aws-cdk-cloud-assembly-schema==48.3.0 # via aws-cdk-lib -aws-cdk-lib==2.207.0 +aws-cdk-lib==2.210.0 # via localstack-core aws-sam-translator==1.99.0 # via @@ -39,17 +39,17 @@ aws-sam-translator==1.99.0 # localstack-core aws-xray-sdk==2.14.0 # via moto-ext -awscli==1.41.14 +awscli==1.42.6 # via localstack-core -awscrt==0.27.4 +awscrt==0.27.5 # via localstack-core -boto3==1.39.14 +boto3==1.40.6 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -botocore==1.39.14 +botocore==1.40.6 # via # aws-xray-sdk # awscli @@ -57,7 +57,7 @@ botocore==1.39.14 # localstack-core # moto-ext # s3transfer -build==1.2.2.post1 +build==1.3.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -66,11 +66,11 @@ cachetools==6.1.0 # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cattrs==24.1.3 +cattrs==25.1.1 # via jsii cbor2==5.6.5 # via localstack-core -certifi==2025.7.14 +certifi==2025.8.3 # via # httpcore # httpx @@ -80,9 +80,9 @@ cffi==1.17.1 # via cryptography cfgv==3.4.0 # via pre-commit -cfn-lint==1.38.0 +cfn-lint==1.38.2 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.3 # via requests click==8.2.1 # via @@ -94,7 +94,7 @@ constantly==23.10.4 # via localstack-twisted constructs==10.4.2 # via aws-cdk-lib -coverage==7.10.1 +coverage==7.10.3 # via # coveralls # localstack-core @@ -102,7 +102,7 @@ coveralls==4.0.1 # via localstack-core (pyproject.toml) crontab==1.0.5 # via localstack-core -cryptography==45.0.5 +cryptography==45.0.6 # via # joserfc # localstack-core @@ -113,7 +113,7 @@ cython==3.1.2 # via localstack-core (pyproject.toml) decorator==5.2.1 # via jsonpath-rw -deepdiff==8.5.0 +deepdiff==8.6.0 # via # localstack-core # localstack-snapshot @@ -168,7 +168,7 @@ hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -identify==2.6.12 +identify==2.6.13 # via pre-commit idna==3.10 # via @@ -195,7 +195,7 @@ joserfc==1.2.2 # via moto-ext jpype1-ext==0.0.2 # via localstack-core -jsii==1.112.0 +jsii==1.113.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 @@ -244,7 +244,7 @@ localstack-snapshot==0.3.0 # via localstack-core localstack-twisted==24.3.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich markupsafe==3.0.2 # via @@ -260,7 +260,7 @@ mpmath==1.3.0 # via sympy multipart==1.3.0 # via moto-ext -mypy==1.17.0 +mypy==1.17.1 # via localstack-core (pyproject.toml) mypy-extensions==1.1.0 # via mypy @@ -308,7 +308,7 @@ pluggy==1.6.0 # pytest plumbum==1.9.0 # via pandoc -plux==1.12.1 +plux==1.13.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -317,7 +317,7 @@ ply==3.11 # jsonpath-ng # jsonpath-rw # pandoc -pre-commit==4.2.0 +pre-commit==4.3.0 # via localstack-core (pyproject.toml) priority==1.3.0 # via @@ -349,7 +349,7 @@ pygments==2.19.2 # via # pytest # rich -pymongo==4.13.2 +pymongo==4.14.0 # via localstack-core pyopenssl==25.1.0 # via @@ -402,7 +402,7 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2025.7.34 # via cfn-lint requests==2.32.4 # via @@ -419,7 +419,7 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 +responses==0.25.8 # via moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator @@ -429,7 +429,7 @@ rich==14.1.0 # localstack-core (pyproject.toml) rolo==0.7.6 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.27.0 # via # jsonschema # referencing @@ -437,7 +437,7 @@ rsa==4.7.2 # via awscli rstr==3.2.2 # via localstack-core (pyproject.toml) -ruff==0.12.5 +ruff==0.12.8 # via localstack-core (pyproject.toml) s3transfer==0.13.1 # via @@ -473,6 +473,7 @@ typing-extensions==4.14.1 # via # anyio # aws-sam-translator + # cattrs # cfn-lint # jsii # localstack-twisted @@ -493,7 +494,7 @@ urllib3==2.5.0 # opensearch-py # requests # responses -virtualenv==20.32.0 +virtualenv==20.33.1 # via pre-commit websocket-client==1.8.0 # via localstack-core diff --git a/requirements-runtime.txt b/requirements-runtime.txt index 7db6558c7d853..ee454d4806a1a 100644 --- a/requirements-runtime.txt +++ b/requirements-runtime.txt @@ -27,17 +27,17 @@ aws-sam-translator==1.99.0 # localstack-core (pyproject.toml) aws-xray-sdk==2.14.0 # via moto-ext -awscli==1.41.14 +awscli==1.42.6 # via localstack-core (pyproject.toml) -awscrt==0.27.4 +awscrt==0.27.5 # via localstack-core -boto3==1.39.14 +boto3==1.40.6 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -botocore==1.39.14 +botocore==1.40.6 # via # aws-xray-sdk # awscli @@ -45,7 +45,7 @@ botocore==1.39.14 # localstack-core # moto-ext # s3transfer -build==1.2.2.post1 +build==1.3.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -56,15 +56,15 @@ cachetools==6.1.0 # localstack-core (pyproject.toml) cbor2==5.6.5 # via localstack-core -certifi==2025.7.14 +certifi==2025.8.3 # via # opensearch-py # requests cffi==1.17.1 # via cryptography -cfn-lint==1.38.0 +cfn-lint==1.38.2 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.3 # via requests click==8.2.1 # via @@ -76,7 +76,7 @@ constantly==23.10.4 # via localstack-twisted crontab==1.0.5 # via localstack-core (pyproject.toml) -cryptography==45.0.5 +cryptography==45.0.6 # via # joserfc # localstack-core @@ -182,7 +182,7 @@ lazy-object-proxy==1.11.0 # via openapi-spec-validator localstack-twisted==24.3.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich markupsafe==3.0.2 # via @@ -221,7 +221,7 @@ parse==1.20.2 # via openapi-core pathable==0.4.4 # via jsonschema-path -plux==1.12.1 +plux==1.13.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -249,7 +249,7 @@ pydantic-core==2.33.2 # via pydantic pygments==2.19.2 # via rich -pymongo==4.13.2 +pymongo==4.14.0 # via localstack-core (pyproject.toml) pyopenssl==25.1.0 # via @@ -285,7 +285,7 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2025.7.34 # via cfn-lint requests==2.32.4 # via @@ -300,7 +300,7 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 +responses==0.25.8 # via moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator @@ -310,7 +310,7 @@ rich==14.1.0 # localstack-core (pyproject.toml) rolo==0.7.6 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.27.0 # via # jsonschema # referencing diff --git a/requirements-test.txt b/requirements-test.txt index 1d71a474d0755..b7ca86efd58be 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -12,7 +12,7 @@ antlr4-python3-runtime==4.13.2 # via # localstack-core # moto-ext -anyio==4.9.0 +anyio==4.10.0 # via httpx apispec==6.8.2 # via localstack-core @@ -29,9 +29,9 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==45.2.0 +aws-cdk-cloud-assembly-schema==48.3.0 # via aws-cdk-lib -aws-cdk-lib==2.207.0 +aws-cdk-lib==2.210.0 # via localstack-core (pyproject.toml) aws-sam-translator==1.99.0 # via @@ -39,17 +39,17 @@ aws-sam-translator==1.99.0 # localstack-core aws-xray-sdk==2.14.0 # via moto-ext -awscli==1.41.14 +awscli==1.42.6 # via localstack-core -awscrt==0.27.4 +awscrt==0.27.5 # via localstack-core -boto3==1.39.14 +boto3==1.40.6 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -botocore==1.39.14 +botocore==1.40.6 # via # aws-xray-sdk # awscli @@ -57,7 +57,7 @@ botocore==1.39.14 # localstack-core # moto-ext # s3transfer -build==1.2.2.post1 +build==1.3.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -66,11 +66,11 @@ cachetools==6.1.0 # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cattrs==24.1.3 +cattrs==25.1.1 # via jsii cbor2==5.6.5 # via localstack-core -certifi==2025.7.14 +certifi==2025.8.3 # via # httpcore # httpx @@ -78,9 +78,9 @@ certifi==2025.7.14 # requests cffi==1.17.1 # via cryptography -cfn-lint==1.38.0 +cfn-lint==1.38.2 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.3 # via requests click==8.2.1 # via @@ -92,11 +92,11 @@ constantly==23.10.4 # via localstack-twisted constructs==10.4.2 # via aws-cdk-lib -coverage==7.10.1 +coverage==7.10.3 # via localstack-core (pyproject.toml) crontab==1.0.5 # via localstack-core -cryptography==45.0.5 +cryptography==45.0.6 # via # joserfc # localstack-core @@ -105,7 +105,7 @@ cryptography==45.0.5 # pyopenssl decorator==5.2.1 # via jsonpath-rw -deepdiff==8.5.0 +deepdiff==8.6.0 # via # localstack-core (pyproject.toml) # localstack-snapshot @@ -179,7 +179,7 @@ joserfc==1.2.2 # via moto-ext jpype1-ext==0.0.2 # via localstack-core -jsii==1.112.0 +jsii==1.113.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 @@ -228,7 +228,7 @@ localstack-snapshot==0.3.0 # via localstack-core (pyproject.toml) localstack-twisted==24.3.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich markupsafe==3.0.2 # via @@ -275,7 +275,7 @@ pluggy==1.6.0 # via # localstack-core (pyproject.toml) # pytest -plux==1.12.1 +plux==1.13.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -313,7 +313,7 @@ pygments==2.19.2 # via # pytest # rich -pymongo==4.13.2 +pymongo==4.14.0 # via localstack-core pyopenssl==25.1.0 # via @@ -363,7 +363,7 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2025.7.34 # via cfn-lint requests==2.32.4 # via @@ -379,7 +379,7 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 +responses==0.25.8 # via moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator @@ -389,7 +389,7 @@ rich==14.1.0 # localstack-core (pyproject.toml) rolo==0.7.6 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.27.0 # via # jsonschema # referencing @@ -429,6 +429,7 @@ typing-extensions==4.14.1 # via # anyio # aws-sam-translator + # cattrs # cfn-lint # jsii # localstack-twisted diff --git a/requirements-typehint.txt b/requirements-typehint.txt index ae72dbd2be51c..fd1b3988a79f8 100644 --- a/requirements-typehint.txt +++ b/requirements-typehint.txt @@ -12,7 +12,7 @@ antlr4-python3-runtime==4.13.2 # via # localstack-core # moto-ext -anyio==4.9.0 +anyio==4.10.0 # via httpx apispec==6.8.2 # via localstack-core @@ -29,9 +29,9 @@ aws-cdk-asset-awscli-v1==2.2.242 # via aws-cdk-lib aws-cdk-asset-node-proxy-agent-v6==2.1.0 # via aws-cdk-lib -aws-cdk-cloud-assembly-schema==45.2.0 +aws-cdk-cloud-assembly-schema==48.3.0 # via aws-cdk-lib -aws-cdk-lib==2.207.0 +aws-cdk-lib==2.210.0 # via localstack-core aws-sam-translator==1.99.0 # via @@ -39,19 +39,19 @@ aws-sam-translator==1.99.0 # localstack-core aws-xray-sdk==2.14.0 # via moto-ext -awscli==1.41.14 +awscli==1.42.6 # via localstack-core -awscrt==0.27.4 +awscrt==0.27.5 # via localstack-core -boto3==1.39.14 +boto3==1.40.6 # via # aws-sam-translator # kclpy-ext # localstack-core # moto-ext -boto3-stubs==1.39.15 +boto3-stubs==1.40.7 # via localstack-core (pyproject.toml) -botocore==1.39.14 +botocore==1.40.6 # via # aws-xray-sdk # awscli @@ -61,7 +61,7 @@ botocore==1.39.14 # s3transfer botocore-stubs==1.38.46 # via boto3-stubs -build==1.2.2.post1 +build==1.3.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -70,11 +70,11 @@ cachetools==6.1.0 # airspeed-ext # localstack-core # localstack-core (pyproject.toml) -cattrs==24.1.3 +cattrs==25.1.1 # via jsii cbor2==5.6.5 # via localstack-core -certifi==2025.7.14 +certifi==2025.8.3 # via # httpcore # httpx @@ -84,9 +84,9 @@ cffi==1.17.1 # via cryptography cfgv==3.4.0 # via pre-commit -cfn-lint==1.38.0 +cfn-lint==1.38.2 # via moto-ext -charset-normalizer==3.4.2 +charset-normalizer==3.4.3 # via requests click==8.2.1 # via @@ -98,7 +98,7 @@ constantly==23.10.4 # via localstack-twisted constructs==10.4.2 # via aws-cdk-lib -coverage==7.10.1 +coverage==7.10.3 # via # coveralls # localstack-core @@ -106,7 +106,7 @@ coveralls==4.0.1 # via localstack-core crontab==1.0.5 # via localstack-core -cryptography==45.0.5 +cryptography==45.0.6 # via # joserfc # localstack-core @@ -117,7 +117,7 @@ cython==3.1.2 # via localstack-core decorator==5.2.1 # via jsonpath-rw -deepdiff==8.5.0 +deepdiff==8.6.0 # via # localstack-core # localstack-snapshot @@ -172,7 +172,7 @@ hyperframe==6.1.0 # via h2 hyperlink==21.0.0 # via localstack-twisted -identify==2.6.12 +identify==2.6.13 # via pre-commit idna==3.10 # via @@ -199,7 +199,7 @@ joserfc==1.2.2 # via moto-ext jpype1-ext==0.0.2 # via localstack-core -jsii==1.112.0 +jsii==1.113.0 # via # aws-cdk-asset-awscli-v1 # aws-cdk-asset-node-proxy-agent-v6 @@ -248,7 +248,7 @@ localstack-snapshot==0.3.0 # via localstack-core localstack-twisted==24.3.0 # via localstack-core -markdown-it-py==3.0.0 +markdown-it-py==4.0.0 # via rich markupsafe==3.0.2 # via @@ -264,213 +264,213 @@ mpmath==1.3.0 # via sympy multipart==1.3.0 # via moto-ext -mypy==1.17.0 +mypy==1.17.1 # via localstack-core -mypy-boto3-acm==1.39.0 +mypy-boto3-acm==1.40.0 # via boto3-stubs -mypy-boto3-acm-pca==1.39.0 +mypy-boto3-acm-pca==1.40.1 # via boto3-stubs -mypy-boto3-amplify==1.39.0 +mypy-boto3-amplify==1.40.0 # via boto3-stubs -mypy-boto3-apigateway==1.39.0 +mypy-boto3-apigateway==1.40.0 # via boto3-stubs -mypy-boto3-apigatewayv2==1.39.0 +mypy-boto3-apigatewayv2==1.40.0 # via boto3-stubs -mypy-boto3-appconfig==1.39.0 +mypy-boto3-appconfig==1.40.0 # via boto3-stubs -mypy-boto3-appconfigdata==1.39.0 +mypy-boto3-appconfigdata==1.40.0 # via boto3-stubs -mypy-boto3-application-autoscaling==1.39.0 +mypy-boto3-application-autoscaling==1.40.0 # via boto3-stubs -mypy-boto3-appsync==1.39.0 +mypy-boto3-appsync==1.40.0 # via boto3-stubs -mypy-boto3-athena==1.39.0 +mypy-boto3-athena==1.40.0 # via boto3-stubs -mypy-boto3-autoscaling==1.39.0 +mypy-boto3-autoscaling==1.40.0 # via boto3-stubs -mypy-boto3-backup==1.39.0 +mypy-boto3-backup==1.40.0 # via boto3-stubs -mypy-boto3-batch==1.39.0 +mypy-boto3-batch==1.40.5 # via boto3-stubs -mypy-boto3-ce==1.39.0 +mypy-boto3-ce==1.40.0 # via boto3-stubs -mypy-boto3-cloudcontrol==1.39.0 +mypy-boto3-cloudcontrol==1.40.0 # via boto3-stubs -mypy-boto3-cloudformation==1.39.0 +mypy-boto3-cloudformation==1.40.0 # via boto3-stubs -mypy-boto3-cloudfront==1.39.10 +mypy-boto3-cloudfront==1.40.5 # via boto3-stubs -mypy-boto3-cloudtrail==1.39.0 +mypy-boto3-cloudtrail==1.40.0 # via boto3-stubs -mypy-boto3-cloudwatch==1.39.0 +mypy-boto3-cloudwatch==1.40.0 # via boto3-stubs -mypy-boto3-codebuild==1.39.0 +mypy-boto3-codebuild==1.40.5 # via boto3-stubs -mypy-boto3-codecommit==1.39.0 +mypy-boto3-codecommit==1.40.0 # via boto3-stubs -mypy-boto3-codeconnections==1.39.0 +mypy-boto3-codeconnections==1.40.2 # via boto3-stubs -mypy-boto3-codedeploy==1.39.0 +mypy-boto3-codedeploy==1.40.0 # via boto3-stubs -mypy-boto3-codepipeline==1.39.0 +mypy-boto3-codepipeline==1.40.0 # via boto3-stubs -mypy-boto3-codestar-connections==1.39.0 +mypy-boto3-codestar-connections==1.40.0 # via boto3-stubs -mypy-boto3-cognito-identity==1.39.0 +mypy-boto3-cognito-identity==1.40.0 # via boto3-stubs -mypy-boto3-cognito-idp==1.39.0 +mypy-boto3-cognito-idp==1.40.7 # via boto3-stubs -mypy-boto3-dms==1.39.0 +mypy-boto3-dms==1.40.0 # via boto3-stubs -mypy-boto3-docdb==1.39.0 +mypy-boto3-docdb==1.40.0 # via boto3-stubs -mypy-boto3-dynamodb==1.39.0 +mypy-boto3-dynamodb==1.40.0 # via boto3-stubs -mypy-boto3-dynamodbstreams==1.39.5 +mypy-boto3-dynamodbstreams==1.40.0 # via boto3-stubs -mypy-boto3-ec2==1.39.14 +mypy-boto3-ec2==1.40.7 # via boto3-stubs -mypy-boto3-ecr==1.39.11 +mypy-boto3-ecr==1.40.0 # via boto3-stubs -mypy-boto3-ecs==1.39.6 +mypy-boto3-ecs==1.40.0 # via boto3-stubs -mypy-boto3-efs==1.39.0 +mypy-boto3-efs==1.40.0 # via boto3-stubs -mypy-boto3-eks==1.39.0 +mypy-boto3-eks==1.40.3 # via boto3-stubs -mypy-boto3-elasticache==1.39.0 +mypy-boto3-elasticache==1.40.0 # via boto3-stubs -mypy-boto3-elasticbeanstalk==1.39.0 +mypy-boto3-elasticbeanstalk==1.40.0 # via boto3-stubs -mypy-boto3-elbv2==1.39.0 +mypy-boto3-elbv2==1.40.0 # via boto3-stubs -mypy-boto3-emr==1.39.11 +mypy-boto3-emr==1.40.0 # via boto3-stubs -mypy-boto3-emr-serverless==1.39.0 +mypy-boto3-emr-serverless==1.40.0 # via boto3-stubs -mypy-boto3-es==1.39.0 +mypy-boto3-es==1.40.0 # via boto3-stubs -mypy-boto3-events==1.39.5 +mypy-boto3-events==1.40.0 # via boto3-stubs -mypy-boto3-firehose==1.39.0 +mypy-boto3-firehose==1.40.0 # via boto3-stubs -mypy-boto3-fis==1.39.0 +mypy-boto3-fis==1.40.0 # via boto3-stubs -mypy-boto3-glacier==1.39.0 +mypy-boto3-glacier==1.40.0 # via boto3-stubs -mypy-boto3-glue==1.39.12 +mypy-boto3-glue==1.40.5 # via boto3-stubs -mypy-boto3-iam==1.39.0 +mypy-boto3-iam==1.40.0 # via boto3-stubs -mypy-boto3-identitystore==1.39.0 +mypy-boto3-identitystore==1.40.0 # via boto3-stubs -mypy-boto3-iot==1.39.0 +mypy-boto3-iot==1.40.0 # via boto3-stubs -mypy-boto3-iot-data==1.39.0 +mypy-boto3-iot-data==1.40.6 # via boto3-stubs -mypy-boto3-iotanalytics==1.39.0 +mypy-boto3-iotanalytics==1.40.0 # via boto3-stubs -mypy-boto3-iotwireless==1.39.7 +mypy-boto3-iotwireless==1.40.0 # via boto3-stubs -mypy-boto3-kafka==1.39.0 +mypy-boto3-kafka==1.40.0 # via boto3-stubs -mypy-boto3-kinesis==1.39.0 +mypy-boto3-kinesis==1.40.0 # via boto3-stubs -mypy-boto3-kinesisanalytics==1.39.0 +mypy-boto3-kinesisanalytics==1.40.0 # via boto3-stubs -mypy-boto3-kinesisanalyticsv2==1.39.0 +mypy-boto3-kinesisanalyticsv2==1.40.0 # via boto3-stubs -mypy-boto3-kms==1.39.14 +mypy-boto3-kms==1.40.0 # via boto3-stubs -mypy-boto3-lakeformation==1.39.0 +mypy-boto3-lakeformation==1.40.0 # via boto3-stubs -mypy-boto3-lambda==1.39.11 +mypy-boto3-lambda==1.40.7 # via boto3-stubs -mypy-boto3-logs==1.39.9 +mypy-boto3-logs==1.40.0 # via boto3-stubs -mypy-boto3-managedblockchain==1.39.0 +mypy-boto3-managedblockchain==1.40.0 # via boto3-stubs -mypy-boto3-mediaconvert==1.39.9 +mypy-boto3-mediaconvert==1.40.0 # via boto3-stubs -mypy-boto3-mediastore==1.39.0 +mypy-boto3-mediastore==1.40.0 # via boto3-stubs -mypy-boto3-mq==1.39.0 +mypy-boto3-mq==1.40.0 # via boto3-stubs -mypy-boto3-mwaa==1.39.0 +mypy-boto3-mwaa==1.40.0 # via boto3-stubs -mypy-boto3-neptune==1.39.0 +mypy-boto3-neptune==1.40.0 # via boto3-stubs -mypy-boto3-opensearch==1.39.5 +mypy-boto3-opensearch==1.40.0 # via boto3-stubs -mypy-boto3-organizations==1.39.0 +mypy-boto3-organizations==1.40.0 # via boto3-stubs -mypy-boto3-pi==1.39.0 +mypy-boto3-pi==1.40.0 # via boto3-stubs -mypy-boto3-pinpoint==1.39.0 +mypy-boto3-pinpoint==1.40.0 # via boto3-stubs -mypy-boto3-pipes==1.39.0 +mypy-boto3-pipes==1.40.0 # via boto3-stubs -mypy-boto3-qldb==1.39.0 +mypy-boto3-qldb==1.40.0 # via boto3-stubs -mypy-boto3-qldb-session==1.39.0 +mypy-boto3-qldb-session==1.40.0 # via boto3-stubs -mypy-boto3-rds==1.39.1 +mypy-boto3-rds==1.40.3 # via boto3-stubs -mypy-boto3-rds-data==1.39.0 +mypy-boto3-rds-data==1.40.0 # via boto3-stubs -mypy-boto3-redshift==1.39.0 +mypy-boto3-redshift==1.40.0 # via boto3-stubs -mypy-boto3-redshift-data==1.39.0 +mypy-boto3-redshift-data==1.40.0 # via boto3-stubs -mypy-boto3-resource-groups==1.39.0 +mypy-boto3-resource-groups==1.40.0 # via boto3-stubs -mypy-boto3-resourcegroupstaggingapi==1.39.0 +mypy-boto3-resourcegroupstaggingapi==1.40.0 # via boto3-stubs -mypy-boto3-route53==1.39.3 +mypy-boto3-route53==1.40.0 # via boto3-stubs -mypy-boto3-route53resolver==1.39.0 +mypy-boto3-route53resolver==1.40.0 # via boto3-stubs -mypy-boto3-s3==1.39.5 +mypy-boto3-s3==1.40.0 # via boto3-stubs -mypy-boto3-s3control==1.39.2 +mypy-boto3-s3control==1.40.0 # via boto3-stubs -mypy-boto3-sagemaker==1.39.10 +mypy-boto3-sagemaker==1.40.6 # via boto3-stubs -mypy-boto3-sagemaker-runtime==1.39.0 +mypy-boto3-sagemaker-runtime==1.40.0 # via boto3-stubs -mypy-boto3-secretsmanager==1.39.0 +mypy-boto3-secretsmanager==1.40.0 # via boto3-stubs -mypy-boto3-serverlessrepo==1.39.0 +mypy-boto3-serverlessrepo==1.40.0 # via boto3-stubs -mypy-boto3-servicediscovery==1.39.0 +mypy-boto3-servicediscovery==1.40.0 # via boto3-stubs -mypy-boto3-ses==1.39.0 +mypy-boto3-ses==1.40.0 # via boto3-stubs -mypy-boto3-sesv2==1.39.9 +mypy-boto3-sesv2==1.40.0 # via boto3-stubs -mypy-boto3-sns==1.39.0 +mypy-boto3-sns==1.40.1 # via boto3-stubs -mypy-boto3-sqs==1.39.14 +mypy-boto3-sqs==1.40.0 # via boto3-stubs -mypy-boto3-ssm==1.39.9 +mypy-boto3-ssm==1.40.0 # via boto3-stubs -mypy-boto3-sso-admin==1.39.0 +mypy-boto3-sso-admin==1.40.7 # via boto3-stubs -mypy-boto3-stepfunctions==1.39.8 +mypy-boto3-stepfunctions==1.40.0 # via boto3-stubs -mypy-boto3-sts==1.39.0 +mypy-boto3-sts==1.40.0 # via boto3-stubs -mypy-boto3-timestream-query==1.39.0 +mypy-boto3-timestream-query==1.40.0 # via boto3-stubs -mypy-boto3-timestream-write==1.39.0 +mypy-boto3-timestream-write==1.40.0 # via boto3-stubs -mypy-boto3-transcribe==1.39.0 +mypy-boto3-transcribe==1.40.6 # via boto3-stubs -mypy-boto3-verifiedpermissions==1.39.0 +mypy-boto3-verifiedpermissions==1.40.0 # via boto3-stubs -mypy-boto3-wafv2==1.39.0 +mypy-boto3-wafv2==1.40.0 # via boto3-stubs -mypy-boto3-xray==1.39.0 +mypy-boto3-xray==1.40.0 # via boto3-stubs mypy-extensions==1.1.0 # via mypy @@ -518,7 +518,7 @@ pluggy==1.6.0 # pytest plumbum==1.9.0 # via pandoc -plux==1.12.1 +plux==1.13.0 # via # localstack-core # localstack-core (pyproject.toml) @@ -527,7 +527,7 @@ ply==3.11 # jsonpath-ng # jsonpath-rw # pandoc -pre-commit==4.2.0 +pre-commit==4.3.0 # via localstack-core priority==1.3.0 # via @@ -559,7 +559,7 @@ pygments==2.19.2 # via # pytest # rich -pymongo==4.13.2 +pymongo==4.14.0 # via localstack-core pyopenssl==25.1.0 # via @@ -612,7 +612,7 @@ referencing==0.36.2 # jsonschema # jsonschema-path # jsonschema-specifications -regex==2024.11.6 +regex==2025.7.34 # via cfn-lint requests==2.32.4 # via @@ -629,7 +629,7 @@ requests==2.32.4 # rolo requests-aws4auth==1.3.1 # via localstack-core -responses==0.25.7 +responses==0.25.8 # via moto-ext rfc3339-validator==0.1.4 # via openapi-schema-validator @@ -639,7 +639,7 @@ rich==14.1.0 # localstack-core (pyproject.toml) rolo==0.7.6 # via localstack-core -rpds-py==0.26.0 +rpds-py==0.27.0 # via # jsonschema # referencing @@ -647,7 +647,7 @@ rsa==4.7.2 # via awscli rstr==3.2.2 # via localstack-core -ruff==0.12.5 +ruff==0.12.8 # via localstack-core s3transfer==0.13.1 # via @@ -679,7 +679,7 @@ typeguard==2.13.3 # aws-cdk-lib # constructs # jsii -types-awscrt==0.27.4 +types-awscrt==0.27.5 # via botocore-stubs types-s3transfer==0.13.0 # via boto3-stubs @@ -688,6 +688,7 @@ typing-extensions==4.14.1 # anyio # aws-sam-translator # boto3-stubs + # cattrs # cfn-lint # jsii # localstack-twisted @@ -811,7 +812,7 @@ urllib3==2.5.0 # opensearch-py # requests # responses -virtualenv==20.32.0 +virtualenv==20.33.1 # via pre-commit websocket-client==1.8.0 # via localstack-core diff --git a/scripts/capture_notimplemented_responses.py b/scripts/capture_notimplemented_responses.py index c8747562a1da5..ff6d517d908fd 100644 --- a/scripts/capture_notimplemented_responses.py +++ b/scripts/capture_notimplemented_responses.py @@ -7,7 +7,7 @@ import traceback from datetime import timedelta from pathlib import Path -from typing import Optional, TypedDict +from typing import TypedDict import botocore.config import requests @@ -150,7 +150,7 @@ def simulate_call(service: str, op: str) -> RowEntry: return result -def _make_api_call(client, service: str, op: str, parameters: Optional[Instance]): +def _make_api_call(client, service: str, op: str, parameters: Instance | None): result = RowEntry(service=service, operation=op, status_code=0) try: response = client._make_api_call(op, parameters) @@ -337,7 +337,7 @@ def calculate_percentages(): implemented_aggregate = {} aggregate_list = [] - with open("./output-notimplemented.csv", "r") as fd: + with open("./output-notimplemented.csv") as fd: reader = csv.DictReader(fd, fieldnames=["service", "operation", "implemented"]) for line in reader: if line["implemented"] == "implemented": diff --git a/scripts/gather_outdated_snapshots.py b/scripts/gather_outdated_snapshots.py index 817ab45a4b661..99982972321da 100644 --- a/scripts/gather_outdated_snapshots.py +++ b/scripts/gather_outdated_snapshots.py @@ -39,7 +39,7 @@ def do_get_outdated_snapshots(path: str): recorded_date = recorded_snapshot_data.get("last_validated_date") date = datetime.datetime.fromisoformat(recorded_date) if date.timestamp() < date_limit: - outdated_snapshot_data = dict() + outdated_snapshot_data = {} if show_date: outdated_snapshot_data["last_validation_date"] = recorded_date if combine_parametrized: diff --git a/scripts/metrics_coverage/diff_metrics_coverage.py b/scripts/metrics_coverage/diff_metrics_coverage.py index 7409582d65471..1036dfa5283b6 100644 --- a/scripts/metrics_coverage/diff_metrics_coverage.py +++ b/scripts/metrics_coverage/diff_metrics_coverage.py @@ -42,7 +42,7 @@ def create_initial_coverage(path_to_initial_metrics: str) -> dict: pathlist = Path(path_to_initial_metrics).rglob("*.csv") coverage = {} for path in pathlist: - with open(path, "r") as csv_obj: + with open(path) as csv_obj: print(f"Processing integration test coverage metrics: {path}") csv_dict_reader = csv.DictReader(csv_obj) for metric in csv_dict_reader: @@ -85,7 +85,7 @@ def mark_coverage_acceptance_test( additional_tested = {} add_to_additional = False for path in pathlist: - with open(path, "r") as csv_obj: + with open(path) as csv_obj: print(f"Processing acceptance test coverage metrics: {path}") csv_dict_reader = csv.DictReader(csv_obj) for metric in csv_dict_reader: diff --git a/scripts/render_marker_report.py b/scripts/render_marker_report.py index 2b1e0e1154825..80fac7d087224 100644 --- a/scripts/render_marker_report.py +++ b/scripts/render_marker_report.py @@ -53,7 +53,7 @@ class EnrichedReport: def load_file(filepath: str) -> str: - with open(filepath, "r") as fd: + with open(filepath) as fd: return fd.read() @@ -133,7 +133,7 @@ def main(): rendered_markdown = render_template( template=load_file(template_path), enriched_report=enriched_report ) - with open(output_path, "wt") as outfile: + with open(output_path, "w") as outfile: outfile.write(rendered_markdown) diff --git a/scripts/tinybird/upload_raw_test_metrics_and_coverage.py b/scripts/tinybird/upload_raw_test_metrics_and_coverage.py index dc578f4b2427c..711dbd93f2d4d 100644 --- a/scripts/tinybird/upload_raw_test_metrics_and_coverage.py +++ b/scripts/tinybird/upload_raw_test_metrics_and_coverage.py @@ -210,7 +210,7 @@ def send_metric_report(metric_path: str, source_type: str, timestamp: str): pathlist = Path(metric_path).rglob("metric-report-raw-data-*.csv") for path in pathlist: print(f"checking {str(path)}") - with open(path, "r") as csv_obj: + with open(path) as csv_obj: reader_obj = csv.DictReader(csv_obj) data_to_remove = [field for field in reader_obj.fieldnames if field not in DATA_TO_KEEP] for row in reader_obj: @@ -259,7 +259,7 @@ def send_implemented_coverage(file: str, timestamp: str, type: str): count: int = 0 build_id = os.environ.get("CIRCLE_WORKFLOW_ID", "") or os.environ.get("GITHUB_RUN_ID", "") - with open(file, "r") as csv_obj: + with open(file) as csv_obj: reader_obj = csv.DictReader(csv_obj) for row in reader_obj: count = count + 1 diff --git a/tests/aws/conftest.py b/tests/aws/conftest.py index 9ee30b5b925a0..0f762b6155042 100644 --- a/tests/aws/conftest.py +++ b/tests/aws/conftest.py @@ -1,5 +1,4 @@ import os -from typing import Optional import pytest from _pytest.config import Config @@ -87,9 +86,7 @@ def infrastructure_setup(cdk_template_path, aws_client): # Note: import needs to be local to avoid CDK import on every test run, which takes quite some time from localstack.testing.scenario.provisioning import InfraProvisioner - def _infrastructure_setup( - namespace: str, force_synth: Optional[bool] = False - ) -> InfraProvisioner: + def _infrastructure_setup(namespace: str, force_synth: bool | None = False) -> InfraProvisioner: """ :param namespace: repo-unique identifier for this CDK app. A directory with this name will be created at `tests/aws/cdk_templates//` diff --git a/tests/aws/scenario/kinesis_firehose/conftest.py b/tests/aws/scenario/kinesis_firehose/conftest.py index 1facb91c31de9..e50dd1c0e7a41 100644 --- a/tests/aws/scenario/kinesis_firehose/conftest.py +++ b/tests/aws/scenario/kinesis_firehose/conftest.py @@ -13,7 +13,7 @@ def read_s3_data(aws_client, bucket_name: str) -> dict[str, str]: keys = [obj.get("Key") for obj in response.get("Contents")] - bucket_data = dict() + bucket_data = {} for key in keys: response = aws_client.s3.get_object(Bucket=bucket_name, Key=key) data = response["Body"].read().decode("utf-8") diff --git a/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py b/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py index faebcdbaa8b5f..4b419e90cd4ac 100644 --- a/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py +++ b/tests/aws/scenario/mythical_mysfits/artefacts/functions/populate_db.py @@ -1,5 +1,4 @@ # Source adapted from: https://github.com/aws-samples/aws-modern-application-workshop/blob/python-cdk -from __future__ import print_function import os diff --git a/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py b/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py index 2d58be787fc83..acf8ec729956e 100644 --- a/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py +++ b/tests/aws/scenario/mythical_mysfits/artefacts/functions/stream_processor.py @@ -2,7 +2,6 @@ # The code to be used as an AWS Lambda function for processing real-time # user click records from Kinesis Firehose and adding additional attributes # to them before they are stored in Amazon S3. -from __future__ import print_function import base64 import json diff --git a/tests/aws/services/apigateway/apigateway_fixtures.py b/tests/aws/services/apigateway/apigateway_fixtures.py index e7d58b40c5ba2..61326ba581cb6 100644 --- a/tests/aws/services/apigateway/apigateway_fixtures.py +++ b/tests/aws/services/apigateway/apigateway_fixtures.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import Dict from localstack.services.apigateway.helpers import ( host_based_url, @@ -12,16 +11,16 @@ # TODO convert the test util functions in this file to pytest fixtures -def assert_response_status(response: Dict, status: int): +def assert_response_status(response: dict, status: int): assert response.get("ResponseMetadata").get("HTTPStatusCode") == status -def assert_response_is_200(response: Dict) -> bool: +def assert_response_is_200(response: dict) -> bool: assert_response_status(response, 200) return True -def assert_response_is_201(response: Dict) -> bool: +def assert_response_is_201(response: dict) -> bool: assert_response_status(response, 201) return True diff --git a/tests/aws/services/apigateway/test_apigateway_basic.py b/tests/aws/services/apigateway/test_apigateway_basic.py index ec03c2b1612bb..e2a7c9efce818 100644 --- a/tests/aws/services/apigateway/test_apigateway_basic.py +++ b/tests/aws/services/apigateway/test_apigateway_basic.py @@ -4,7 +4,7 @@ import os import re from collections import namedtuple -from typing import Callable, Optional +from collections.abc import Callable import botocore import pytest @@ -409,7 +409,7 @@ def _test_api_gateway_lambda_proxy_integration_no_asserts( path: str, role_arn: str, apigw_client, - data_mutator_fn: Optional[Callable] = None, + data_mutator_fn: Callable | None = None, ) -> ApiGatewayLambdaProxyIntegrationTestResult: """ Perform the setup needed to do a POST against a Lambda Proxy Integration; @@ -482,9 +482,7 @@ def _test_api_gateway_lambda_proxy_integration( try: parsed_body = json.loads(to_str(result.content)) except json.decoder.JSONDecodeError as e: - raise Exception( - "Couldn't json-decode content: {}".format(to_str(result.content)) - ) from e + raise Exception(f"Couldn't json-decode content: {to_str(result.content)}") from e assert parsed_body.get("return_status_code") == 203 assert parsed_body.get("return_headers") == {"foo": "bar123"} assert parsed_body.get("queryStringParameters") == {"foo": "foo", "bar": "baz"} @@ -717,8 +715,8 @@ def test_apigateway_with_custom_authorization_method( restApiId=api_id, name="lambda_authorizer", type="TOKEN", - authorizerUri="arn:aws:apigateway:us-east-1:lambda:path/ \ - 2015-03-31/functions/{}/invocations".format(lambda_uri), + authorizerUri=f"arn:aws:apigateway:us-east-1:lambda:path/ \ + 2015-03-31/functions/{lambda_uri}/invocations", identitySource="method.request.header.Auth", ) snapshot.match("authorizer", authorizer) diff --git a/tests/aws/services/apigateway/test_apigateway_integrations.py b/tests/aws/services/apigateway/test_apigateway_integrations.py index 1b3c93c9367cc..3ca67d9cc7a9e 100644 --- a/tests/aws/services/apigateway/test_apigateway_integrations.py +++ b/tests/aws/services/apigateway/test_apigateway_integrations.py @@ -14,7 +14,9 @@ from localstack import config from localstack.aws.api.apigateway import IntegrationType +from localstack.aws.api.ec2 import VpcEndpoint from localstack.aws.api.lambda_ import Runtime +from localstack.config import in_docker from localstack.constants import APPLICATION_JSON from localstack.services.lambda_.networking import get_main_endpoint_from_container from localstack.testing.aws.util import is_aws_cloud @@ -914,9 +916,14 @@ def _create(**kwargs): "$..endpointConfiguration.types", "$..policy.Statement..Resource", "$..endpointConfiguration.ipAddressType", + "$.endpoint-details.ServiceRegion", ] ) @markers.aws.validated +@pytest.mark.skipif( + not is_aws_cloud() and not in_docker(), + reason="calling the vpce from a lambda requires LocalStack to run in docker", +) def test_create_execute_api_vpc_endpoint( create_rest_api_with_integration, dynamodb_create_table, @@ -926,6 +933,7 @@ def test_create_execute_api_vpc_endpoint( ec2_create_security_group, snapshot, aws_client, + region_name, ): poll_sleep = 5 if is_aws_cloud() else 1 # TODO: create a re-usable ec2_api() transformer @@ -955,7 +963,6 @@ def test_create_execute_api_vpc_endpoint( request_templates = {APPLICATION_JSON: json.dumps({"TableName": table_name})} # deploy REST API with integration - region_name = aws_client.apigateway.meta.region_name integration_uri = f"arn:aws:apigateway:{region_name}:dynamodb:action/Scan" api_id = create_rest_api_with_integration( integration_uri=integration_uri, @@ -971,7 +978,10 @@ def test_create_execute_api_vpc_endpoint( # create security group vpc_id = default_vpc["VpcId"] security_group = ec2_create_security_group( - VpcId=vpc_id, Description="Test SG for API GW", ports=[443] + VpcId=vpc_id, + Description="Test SG for API GW", + GroupName=f"test-sg-{short_uid()}", + ports=[443], ) security_group = security_group["GroupId"] subnets = aws_client.ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]) @@ -994,15 +1004,16 @@ def test_create_execute_api_vpc_endpoint( # wait until VPC endpoint is in state "available" def _check_available(): result = aws_client.ec2.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id]) - endpoint_details = result["VpcEndpoints"][0] + _endpoint_details = result["VpcEndpoints"][0] # may have multiple entries in AWS - endpoint_details["DnsEntries"] = endpoint_details["DnsEntries"][:1] - endpoint_details.pop("SubnetIds", None) - endpoint_details.pop("NetworkInterfaceIds", None) - assert endpoint_details["State"] == "available" - snapshot.match("endpoint-details", endpoint_details) + _endpoint_details["DnsEntries"] = _endpoint_details["DnsEntries"][:1] + _endpoint_details.pop("SubnetIds", None) + _endpoint_details.pop("NetworkInterfaceIds", None) + assert _endpoint_details["State"] == "available" + snapshot.match("endpoint-details", _endpoint_details) + return _endpoint_details - retry(_check_available, retries=30, sleep=poll_sleep) + endpoint_details: VpcEndpoint = retry(_check_available, retries=30, sleep=poll_sleep) # update API with VPC endpoint patches = [ @@ -1012,21 +1023,15 @@ def _check_available(): aws_client.apigateway.update_rest_api(restApiId=api_id, patchOperations=patches) # create Lambda that invokes API via VPC endpoint (required as the endpoint is only accessible within the VPC) - subdomain = f"{api_id}-{endpoint_id}" - endpoint = api_invoke_url(subdomain, stage=DEFAULT_STAGE_NAME, path="/test") - host_header = urlparse(endpoint).netloc - - # create Lambda function that invokes the API GW (private VPC endpoint not accessible from outside of AWS) - if not is_aws_cloud(): - api_host = get_main_endpoint_from_container() - endpoint = endpoint.replace(host_header, f"{api_host}:{config.GATEWAY_LISTEN[0].port}") lambda_code = textwrap.dedent( - f""" + """ def handler(event, context): import requests - headers = {{"content-type": "application/json", "host": "{host_header}"}} - result = requests.post("{endpoint}", headers=headers) - return {{"content": result.content.decode("utf-8"), "code": result.status_code}} + url = event["url"] + headers = event["headers"] + + result = requests.post(url, headers=headers) + return {"content": result.content.decode("utf-8"), "code": result.status_code} """ ) func_name = f"test-{short_uid()}" @@ -1064,14 +1069,61 @@ def handler(event, context): aws_client.apigateway, restApiId=api_id, stageName=DEFAULT_STAGE_NAME ) - def _invoke_api(): - invoke_response = aws_client.lambda_.invoke(FunctionName=func_name, Payload="{}") + subdomain = f"{api_id}-{endpoint_id}" + endpoint = api_invoke_url(subdomain, stage=DEFAULT_STAGE_NAME, path="/test") + host_header = urlparse(endpoint).netloc + + # create Lambda function that invokes the API GW (private VPC endpoint not accessible from outside of AWS) + if not is_aws_cloud(): + api_host = get_main_endpoint_from_container() + endpoint = endpoint.replace(host_header, f"{api_host}:{config.GATEWAY_LISTEN[0].port}") + + def _invoke_api(url: str, headers: dict[str, str]): + invoke_response = aws_client.lambda_.invoke( + FunctionName=func_name, Payload=json.dumps({"url": url, "headers": headers}) + ) payload = json.load(invoke_response["Payload"]) items = json.loads(payload["content"])["Items"] assert len(items) == len(item_ids) # invoke Lambda and assert result - retry(_invoke_api, retries=15, sleep=poll_sleep) + # AWS + # url: https://{rest-api-id}-{vpce-id}.execute-api.{region}.amazonaws.com/{stage} + # host: {rest-api-id}.execute-api.{region}.amazonaws.com + # LocalStack + # url: http://localhost.localstack.cloud:4566/{stage} + # host: {rest-api-id}-{vpce-id}.execute-api.localhost.localstack.cloud + retry(lambda: _invoke_api(endpoint, {"host": host_header}), retries=15, sleep=poll_sleep) + + # invoke Lambda and assert result + # AWS + # url: https://{public-dns-hostname}.execute-api.{region}.vpce.amazonaws.com/{stage} + # x-apigw-api-id: {rest-api-id} + # LocalStack + # url: http://{public-dns-hostname}.execute-api.{region}.vpce.{localstack-host}/{stage} + # x-apigw-api-id: {rest-api-id} + protocol = "https" if is_aws_cloud() else "http" + vpc_endpoint_public_dns = endpoint_details["DnsEntries"][0]["DnsName"] + public_dns_url = f"{protocol}://{vpc_endpoint_public_dns}/{DEFAULT_STAGE_NAME}/test" + retry( + lambda: _invoke_api(public_dns_url, {"x-apigw-api-id": api_id}), + retries=15, + sleep=poll_sleep, + ) + + # invoke Lambda and assert result + # AWS + # url: https://{public-dns-hostname}.execute-api.{region}.vpce.amazonaws.com/{stage} + # host: {rest-api-id}.execute-api.{region}.amazonaws.com + # LocalStack + # url: http://{public-dns-hostname}.execute-api.{region}.vpce.{localstack_host}/{stage} + # host: {rest-api-id}.execute-api.{region}.{localstack-host} + host = api_invoke_url(api_id).partition("//")[-1].strip("/") + retry( + lambda: _invoke_api(public_dns_url, {"Host": host}), + retries=15, + sleep=poll_sleep, + ) @pytest.mark.skipif( diff --git a/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json b/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json index 3b4a1be1aebdf..e92742b3d7768 100644 --- a/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json +++ b/tests/aws/services/apigateway/test_apigateway_integrations.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": { - "recorded-date": "15-04-2024, 23:07:07", + "recorded-date": "30-07-2025, 17:56:57", "recorded-content": { "endpoint-details": { "CreationTimestamp": "timestamp", @@ -35,6 +35,7 @@ "RequesterManaged": false, "RouteTableIds": [], "ServiceName": "com.amazonaws..execute-api", + "ServiceRegion": "", "State": "available", "Tags": [], "VpcEndpointId": "", @@ -46,6 +47,7 @@ "createdDate": "datetime", "disableExecuteApiEndpoint": false, "endpointConfiguration": { + "ipAddressType": "dualstack", "types": [ "PRIVATE" ], diff --git a/tests/aws/services/apigateway/test_apigateway_integrations.validation.json b/tests/aws/services/apigateway/test_apigateway_integrations.validation.json index 93c003bd54660..aa95dd450d738 100644 --- a/tests/aws/services/apigateway/test_apigateway_integrations.validation.json +++ b/tests/aws/services/apigateway/test_apigateway_integrations.validation.json @@ -12,7 +12,13 @@ "last_validated_date": "2024-12-11T15:28:54+00:00" }, "tests/aws/services/apigateway/test_apigateway_integrations.py::test_create_execute_api_vpc_endpoint": { - "last_validated_date": "2024-04-15T23:07:07+00:00" + "last_validated_date": "2025-07-30T17:57:02+00:00", + "durations_in_seconds": { + "setup": 12.89, + "call": 1064.8, + "teardown": 4.99, + "total": 1082.68 + } }, "tests/aws/services/apigateway/test_apigateway_integrations.py::test_integration_mock_with_path_param": { "last_validated_date": "2024-11-29T19:27:54+00:00" diff --git a/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py b/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py index 9bf6a05ff96ea..dd5bddcf5b814 100644 --- a/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py +++ b/tests/aws/services/cloudcontrol/test_cloudcontrol_api.py @@ -1,6 +1,7 @@ import json import logging -from typing import Callable, ParamSpec, TypeVar +from collections.abc import Callable +from typing import ParamSpec, TypeVar import jsonpatch import pytest diff --git a/tests/aws/services/cloudformation/__init__.py b/tests/aws/services/cloudformation/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/api/__init__.py b/tests/aws/services/cloudformation/api/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/api/test_changesets.py b/tests/aws/services/cloudformation/api/test_changesets.py index 1f397310f5d21..fe63891182ef1 100644 --- a/tests/aws/services/cloudformation/api/test_changesets.py +++ b/tests/aws/services/cloudformation/api/test_changesets.py @@ -4,9 +4,15 @@ import pytest from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.api.test_stacks import ( + MINIMAL_TEMPLATE, +) +from tests.aws.services.cloudformation.conftest import ( + skip_if_v2_provider, + skipped_v2_items, +) from localstack.aws.connect import ServiceLevelClientFactory -from localstack.services.cloudformation.v2.utils import is_v2_engine from localstack.testing.aws.cloudformation_utils import ( load_template_file, load_template_raw, @@ -16,9 +22,6 @@ from localstack.testing.pytest import markers from localstack.utils.strings import short_uid from localstack.utils.sync import ShortCircuitWaitException, poll_condition, wait_until -from tests.aws.services.cloudformation.api.test_stacks import ( - MINIMAL_TEMPLATE, -) class TestUpdates: @@ -62,9 +65,6 @@ def test_simple_update_single_resource( res.destroy() - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Not working in v2 yet" - ) @markers.aws.validated def test_simple_update_two_resources( self, aws_client: ServiceLevelClientFactory, deploy_cfn_template @@ -111,9 +111,6 @@ def test_simple_update_two_resources( # TODO: the error response is incorrect, however the test is otherwise validated and raises # an error because the SSM parameter has been deleted (removed from the stack). @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Test fails with the old engine" - ) def test_deleting_resource( self, aws_client: ServiceLevelClientFactory, deploy_cfn_template, snapshot ): @@ -285,11 +282,11 @@ def test_create_change_set_update_without_parameters( cleanup_stacks(stacks=[stack_id]) -# def test_create_change_set_with_template_url(): -# pass - - -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="change set type not implemented") +# TODO: Key error during deletion +# File "/Users/simon/work/localstack/localstack/localstack-core/localstack/services/cloudformation/v2/provider.py", line 162, in find_change_set_v2 +# return state.change_sets[change_set_name] +# ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ +# KeyError: 'arn:aws:cloudformation:us-east-1:000000000000:changeSet/change-set-926829fe/d065e78c' @markers.aws.validated def test_create_change_set_create_existing(cleanup_changesets, cleanup_stacks, aws_client): """tries to create an already existing stack""" @@ -389,6 +386,7 @@ def test_create_change_set_missing_stackname(aws_client): ) +@skip_if_v2_provider("Resolve") @markers.aws.validated def test_create_change_set_with_ssm_parameter( cleanup_changesets, @@ -636,7 +634,8 @@ def test_create_and_then_remove_non_supported_resource_change_set(deploy_cfn_tem ) -@markers.aws.validated +@skip_if_v2_provider("Test", reason="test needs fixing") +@markers.aws.needs_fixing def test_create_and_then_update_refreshes_template_metadata( aws_client, cleanup_changesets, @@ -763,6 +762,10 @@ def assert_bucket_gone(): "$..IncludeNestedStacks", "$..Parameters", ] + + skipped_v2_items( + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + ) ) @markers.aws.validated def test_empty_changeset(snapshot, cleanups, aws_client): @@ -967,6 +970,10 @@ def test_create_while_in_review(aws_client, snapshot, cleanups): @markers.snapshot.skip_snapshot_verify( paths=["$..Capabilities", "$..IncludeNestedStacks", "$..NotificationARNs", "$..Parameters"] + + skipped_v2_items( + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + ) ) @markers.aws.validated def test_multiple_create_changeset(aws_client, snapshot, cleanups): @@ -1003,7 +1010,13 @@ def test_multiple_create_changeset(aws_client, snapshot, cleanups): ) -@markers.snapshot.skip_snapshot_verify(paths=["$..LastUpdatedTime", "$..StackStatusReason"]) +@markers.snapshot.skip_snapshot_verify( + paths=["$..LastUpdatedTime", "$..StackStatusReason"] + + skipped_v2_items( + # TODO + "$..Capabilities", + ) +) @markers.aws.validated def test_create_changeset_with_stack_id(aws_client, snapshot, cleanups): """ @@ -1085,6 +1098,10 @@ def test_create_changeset_with_stack_id(aws_client, snapshot, cleanups): "$..StatusReason", "$..StackStatusReason", ] + + skipped_v2_items( + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + ), ) @markers.aws.validated def test_name_conflicts(aws_client, snapshot, cleanups): diff --git a/tests/aws/services/cloudformation/api/test_changesets.snapshot.json b/tests/aws/services/cloudformation/api/test_changesets.snapshot.json index 58d492c1180cc..cd9d360d422fe 100644 --- a/tests/aws/services/cloudformation/api/test_changesets.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_changesets.snapshot.json @@ -498,5 +498,20 @@ } } } + }, + "tests/aws/services/cloudformation/api/test_changesets.py::TestUpdates::test_deleting_resource": { + "recorded-date": "02-06-2025, 10:29:41", + "recorded-content": { + "get-parameter-error": { + "Error": { + "Code": "ParameterNotFound", + "Message": "" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_nested_stacks.py b/tests/aws/services/cloudformation/api/test_nested_stacks.py index f6b622bc65fd0..746eff9383426 100644 --- a/tests/aws/services/cloudformation/api/test_nested_stacks.py +++ b/tests/aws/services/cloudformation/api/test_nested_stacks.py @@ -2,6 +2,7 @@ import pytest from botocore.exceptions import ClientError, WaiterError +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider from localstack import config from localstack.testing.aws.util import is_aws_cloud @@ -294,6 +295,7 @@ def test_nested_stacks_conditions(deploy_cfn_template, s3_create_bucket, aws_cli assert ":" not in nested_stack["Stacks"][0]["StackName"] +@skip_if_v2_provider("Deletion") @markers.aws.validated def test_deletion_of_failed_nested_stack(s3_create_bucket, aws_client, region_name, snapshot): """ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py b/tests/aws/services/cloudformation/api/test_resources.py similarity index 85% rename from tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py rename to tests/aws/services/cloudformation/api/test_resources.py index ee6f8e960568a..da125ffa7c8c3 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py +++ b/tests/aws/services/cloudformation/api/test_resources.py @@ -3,10 +3,12 @@ import pytest from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider from localstack.testing.pytest import markers +@skip_if_v1_provider("Not implemented for v1") @markers.aws.validated def test_describe_non_existent_stack(aws_client, deploy_cfn_template, snapshot): with pytest.raises(ClientError) as err: @@ -20,7 +22,7 @@ def test_describe_non_existent_stack(aws_client, deploy_cfn_template, snapshot): @markers.aws.validated def test_describe_non_existent_resource(aws_client, deploy_cfn_template, snapshot): template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" + os.path.dirname(__file__), "../../../templates/ssm_parameter_defaultname.yaml" ) stack = deploy_cfn_template(template_path=template_path, parameters={"Input": "myvalue"}) snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) @@ -33,6 +35,7 @@ def test_describe_non_existent_resource(aws_client, deploy_cfn_template, snapsho snapshot.match("error", err.value) +@skip_if_v1_provider("Not implemented for v1") @markers.aws.validated def test_invalid_logical_resource_id(deploy_cfn_template, snapshot): template = { diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.snapshot.json b/tests/aws/services/cloudformation/api/test_resources.snapshot.json similarity index 67% rename from tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.snapshot.json rename to tests/aws/services/cloudformation/api/test_resources.snapshot.json index 6c6ec67947625..7bf7bc35886b5 100644 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_resources.snapshot.json @@ -1,17 +1,17 @@ { - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py::test_describe_non_existent_resource": { + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_resource": { "recorded-date": "25-07-2025, 22:01:35", "recorded-content": { "error": "An error occurred (ValidationError) when calling the DescribeStackResource operation: Resource not-a-valid-resource does not exist for stack " } }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py::test_describe_non_existent_stack": { + "tests/aws/services/cloudformation/api/test_resources.py::test_describe_non_existent_stack": { "recorded-date": "25-07-2025, 22:02:38", "recorded-content": { "error": "An error occurred (ValidationError) when calling the DescribeStackResource operation: Stack 'not-a-valid-stack' does not exist" } }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.py::test_invalid_logical_resource_id": { + "tests/aws/services/cloudformation/api/test_resources.py::test_invalid_logical_resource_id": { "recorded-date": "25-07-2025, 22:21:31", "recorded-content": { "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template format error: Resource name my-bad-resource-id is non alphanumeric." diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.validation.json b/tests/aws/services/cloudformation/api/test_resources.validation.json similarity index 100% rename from tests/aws/services/cloudformation/v2/ported_from_v1/api/test_resources.validation.json rename to tests/aws/services/cloudformation/api/test_resources.validation.json diff --git a/tests/aws/services/cloudformation/api/test_stacks.py b/tests/aws/services/cloudformation/api/test_stacks.py index 34aaa73a9308e..7e15e4cb99172 100644 --- a/tests/aws/services/cloudformation/api/test_stacks.py +++ b/tests/aws/services/cloudformation/api/test_stacks.py @@ -1,3 +1,4 @@ +import copy import json import os from collections import OrderedDict @@ -6,8 +7,13 @@ import botocore.exceptions import pytest import yaml -from botocore.exceptions import WaiterError +from botocore.exceptions import ClientError, WaiterError from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import ( + skip_if_v1_provider, + skip_if_v2_provider, + skipped_v2_items, +) from localstack.aws.api.cloudformation import Capability from localstack.services.cloudformation.engine.entities import StackIdentifier @@ -22,6 +28,9 @@ class TestStacksApi: @markers.snapshot.skip_snapshot_verify( paths=["$..ChangeSetId", "$..EnableTerminationProtection"] + + skipped_v2_items( + "$..Parameters", + ), ) @markers.aws.validated def test_stack_lifecycle(self, deploy_cfn_template, snapshot, aws_client): @@ -89,6 +98,65 @@ def test_stack_description_special_chars(self, deploy_cfn_template, snapshot, aw ] snapshot.match("describe_stack", response) + @skip_if_v1_provider(reason="Lots of fields not in parity") + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..ResourceChange.Details", + "$..ResourceChange.Scope", + "$..StackStatusReason", + ] + ) + def test_stack_description_lifecycle(self, snapshot, aws_client, cleanups): + """ + Test when and how the description gets set + """ + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "test .test.net", + "Resources": { + "TestResource": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "DummyValue", + }, + } + }, + } + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + change_set = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + ChangeSetType="CREATE", + TemplateBody=json.dumps(template), + ) + change_set_id = change_set["Id"] + stack_id = change_set["StackId"] + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_id, StackName=stack_id + ) + change_set_description = aws_client.cloudformation.describe_change_set( + ChangeSetName=change_set_id + ) + snapshot.match("change-set-pre-execute", change_set_description) + stack_description = aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][ + 0 + ] + snapshot.match("stack-pre-execute", stack_description) + + aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) + + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) + stack_description = aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][ + 0 + ] + snapshot.match("stack-post-execute", stack_description) + @markers.aws.validated def test_stack_name_creation(self, deploy_cfn_template, snapshot, aws_client): snapshot.add_transformer(snapshot.transform.cloudformation_api()) @@ -208,45 +276,6 @@ def test_stack_update_resources( resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name) snapshot.match("stack_resources", resources) - @markers.aws.needs_fixing - def test_list_stack_resources_for_removed_resource(self, deploy_cfn_template, aws_client): - template_path = os.path.join( - os.path.dirname(__file__), "../../../templates/eventbridge_policy.yaml" - ) - event_bus_name = f"bus-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, - parameters={"EventBusName": event_bus_name}, - ) - - resources = aws_client.cloudformation.list_stack_resources(StackName=stack.stack_name)[ - "StackResourceSummaries" - ] - resources_before = len(resources) - assert resources_before == 3 - statuses = {res["ResourceStatus"] for res in resources} - assert statuses == {"CREATE_COMPLETE"} - - # remove one resource from the template, then update stack (via change set) - template_dict = parse_yaml(load_file(template_path)) - template_dict["Resources"].pop("eventPolicy2") - template2 = yaml.dump(template_dict) - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template=template2, - parameters={"EventBusName": event_bus_name}, - ) - - # get list of stack resources, again - make sure that deleted resource is not contained in result - resources = aws_client.cloudformation.list_stack_resources(StackName=stack.stack_name)[ - "StackResourceSummaries" - ] - assert len(resources) == resources_before - 1 - statuses = {res["ResourceStatus"] for res in resources} - assert statuses == {"UPDATE_COMPLETE"} - @markers.aws.validated def test_update_stack_with_same_template_withoutchange( self, deploy_cfn_template, aws_client, snapshot @@ -266,6 +295,7 @@ def test_update_stack_with_same_template_withoutchange( snapshot.match("no_change_exception", ctx.value.response) + @skip_if_v2_provider("Transform") @markers.aws.validated def test_update_stack_with_same_template_withoutchange_transformation( self, deploy_cfn_template, aws_client @@ -338,7 +368,7 @@ def test_failure_options_for_stack_creation( self, rollback_disabled, length_expected, aws_client ): template_with_error = open( - os.path.join(os.path.dirname(__file__), "../../../templates/multiple_bucket.yaml"), "r" + os.path.join(os.path.dirname(__file__), "../../../templates/multiple_bucket.yaml") ).read() stack_name = f"stack-{short_uid()}" @@ -382,7 +412,6 @@ def test_failure_options_for_stack_update( os.path.join( os.path.dirname(__file__), "../../../templates/multiple_bucket_update.yaml" ), - "r", ).read() aws_client.cloudformation.create_stack( @@ -436,7 +465,6 @@ def test_create_stack_with_custom_id( ) template = open( os.path.join(os.path.dirname(__file__), "../../../templates/sns_topic_simple.yaml"), - "r", ).read() stack = aws_client.cloudformation.create_stack( @@ -732,6 +760,7 @@ def test_blocked_stack_deletion(aws_client, cleanups, snapshot): @markers.snapshot.skip_snapshot_verify( paths=["$..EnableTerminationProtection", "$..LastUpdatedTime"] + + skipped_v2_items("$..Capabilities") ) @markers.aws.validated def test_name_conflicts(aws_client, snapshot, cleanups): @@ -922,6 +951,89 @@ def test_stack_deploy_order(deploy_cfn_template, aws_client, snapshot, deploy_or snapshot.match("events", filtered_events) +@skip_if_v1_provider("Not supported with v1 provider") +@markers.aws.validated +@pytest.mark.parametrize( + "deletions", + [ + pytest.param(["C"], id="C"), + pytest.param(["B", "C"], id="B-C"), + pytest.param(["A", "B", "C"], id="A-B-C"), + ], +) +@markers.snapshot.skip_snapshot_verify( + paths=[ + "delete-describe.ChangeSetId", + "$..EnableTerminationProtection", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..PolicyAction", + "$..PhysicalResourceId", + "$..Changes..ResourceChange.BeforeContext.Properties.Value", + "$..StackEvents..EventId", + "$..StackEvents..ResourceStatusReason", + "$..StackEvents..ResourceProperties.Value", + "all-events..EventId", + ] +) +def test_stack_deletion_order( + aws_client, capture_update_process, capture_resource_state_changes, snapshot, deletions +): + t1 = { + "Resources": { + "Dummy": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "dummy", + }, + }, + "A": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "root", + }, + "DependsOn": ["Dummy"], + }, + "B": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "A", + }, + }, + }, + "C": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": { + "Ref": "B", + }, + }, + }, + } + } + t2 = copy.deepcopy(t1) + for deletion in deletions: + del t2["Resources"][deletion] + + stack_id = capture_update_process(snapshot, t1, t2) + + # since resource deployments are serializable, we can capture events and check parity with them + events = list(capture_resource_state_changes(stack_id)) + to_snapshot = [(every["LogicalResourceId"], every["ResourceStatus"]) for every in events[::-1]] + snapshot.match("all-events", to_snapshot) + + +@skip_if_v2_provider("DescribeStack") @markers.snapshot.skip_snapshot_verify( paths=[ # TODO: this property is present in the response from LocalStack when @@ -943,7 +1055,7 @@ def test_no_echo_parameter(snapshot, aws_client, deploy_cfn_template): snapshot.add_transformer(SortingTransformer("Parameters", lambda x: x.get("ParameterKey", ""))) template_path = os.path.join(os.path.dirname(__file__), "../../../templates/cfn_no_echo.yml") - template = open(template_path, "r").read() + template = open(template_path).read() deployment = deploy_cfn_template( template=template, @@ -1078,3 +1190,14 @@ def test_non_existing_stack_message(aws_client, snapshot): snapshot.add_transformer(snapshot.transform.regex("non-existing", "")) snapshot.match("Error", ex.value.response) + + +@skip_if_v1_provider("Not implemented for V1 provider") +@markers.aws.validated +def test_no_parameters_given(aws_client, deploy_cfn_template, snapshot): + template_path = os.path.join( + os.path.dirname(__file__), "../../../templates/ssm_parameter_defaultname.yaml" + ) + with pytest.raises(ClientError) as exc_info: + deploy_cfn_template(template_path=template_path) + snapshot.match("deploy-error", exc_info.value) diff --git a/tests/aws/services/cloudformation/api/test_stacks.snapshot.json b/tests/aws/services/cloudformation/api/test_stacks.snapshot.json index d10231e8c25db..9872036b69c08 100644 --- a/tests/aws/services/cloudformation/api/test_stacks.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_stacks.snapshot.json @@ -1,6 +1,6 @@ { "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "recorded-date": "05-08-2022, 13:03:43", + "recorded-date": "08-08-2025, 15:32:46", "recorded-content": { "describe_stack": { "Capabilities": [ @@ -2287,6 +2287,12 @@ } } }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_parameters_given": { + "recorded-date": "31-07-2025, 16:12:14", + "recorded-content": { + "deploy-error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Parameters: [Input] must have values" + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": { "recorded-date": "21-07-2025, 18:00:27", "recorded-content": { @@ -2302,5 +2308,1832 @@ } } } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[C]": { + "recorded-date": "01-08-2025, 16:11:25", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "dummy", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "A": [ + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VcYe9926zdSX", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "B": [ + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-4E7MEkwnP10J", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "C": [ + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-BzkgOdfSMyjL", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Dummy": [ + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-89p1ywocoTYF", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + }, + "all-events": [ + [ + "", + "REVIEW_IN_PROGRESS" + ], + [ + "", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_COMPLETE" + ], + [ + "A", + "CREATE_IN_PROGRESS" + ], + [ + "A", + "CREATE_COMPLETE" + ], + [ + "B", + "CREATE_IN_PROGRESS" + ], + [ + "B", + "CREATE_COMPLETE" + ], + [ + "C", + "CREATE_IN_PROGRESS" + ], + [ + "C", + "CREATE_COMPLETE" + ], + [ + "", + "CREATE_COMPLETE" + ], + [ + "", + "UPDATE_IN_PROGRESS" + ], + [ + "C", + "DELETE_IN_PROGRESS" + ], + [ + "C", + "DELETE_COMPLETE" + ], + [ + "", + "UPDATE_COMPLETE" + ], + [ + "", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_COMPLETE" + ], + [ + "A", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_COMPLETE" + ], + [ + "Dummy", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_COMPLETE" + ], + [ + "", + "DELETE_COMPLETE" + ] + ] + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[B-C]": { + "recorded-date": "01-08-2025, 16:11:54", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "dummy", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "A": [ + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-VHDXP6O2wX1c", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "B": [ + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-cqwh82b6Ge6W", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "C": [ + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-RdurGWIjXPQz", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Dummy": [ + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-jNZLIqoRVObD", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + }, + "all-events": [ + [ + "", + "REVIEW_IN_PROGRESS" + ], + [ + "", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_COMPLETE" + ], + [ + "A", + "CREATE_IN_PROGRESS" + ], + [ + "A", + "CREATE_COMPLETE" + ], + [ + "B", + "CREATE_IN_PROGRESS" + ], + [ + "B", + "CREATE_COMPLETE" + ], + [ + "C", + "CREATE_IN_PROGRESS" + ], + [ + "C", + "CREATE_COMPLETE" + ], + [ + "", + "CREATE_COMPLETE" + ], + [ + "", + "UPDATE_IN_PROGRESS" + ], + [ + "C", + "DELETE_IN_PROGRESS" + ], + [ + "C", + "DELETE_COMPLETE" + ], + [ + "B", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_COMPLETE" + ], + [ + "", + "UPDATE_COMPLETE" + ], + [ + "", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_COMPLETE" + ], + [ + "Dummy", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_COMPLETE" + ], + [ + "", + "DELETE_COMPLETE" + ] + ] + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[A-B-C]": { + "recorded-date": "01-08-2025, 16:12:23", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "dummy", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "A", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "B", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "C", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Dummy", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "root", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "A": [ + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "A", + "PhysicalResourceId": "CFN-A-Kdps7TfbhFx5", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "B": [ + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "B", + "PhysicalResourceId": "CFN-B-1rCsPWv4xQPK", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "C": [ + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "C", + "PhysicalResourceId": "CFN-C-MlTnjhK28Wet", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Dummy": [ + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Dummy", + "PhysicalResourceId": "CFN-Dummy-Gw0J4gFI8T7M", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + }, + "all-events": [ + [ + "", + "REVIEW_IN_PROGRESS" + ], + [ + "", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_IN_PROGRESS" + ], + [ + "Dummy", + "CREATE_COMPLETE" + ], + [ + "A", + "CREATE_IN_PROGRESS" + ], + [ + "A", + "CREATE_COMPLETE" + ], + [ + "B", + "CREATE_IN_PROGRESS" + ], + [ + "B", + "CREATE_COMPLETE" + ], + [ + "C", + "CREATE_IN_PROGRESS" + ], + [ + "C", + "CREATE_COMPLETE" + ], + [ + "", + "CREATE_COMPLETE" + ], + [ + "", + "UPDATE_IN_PROGRESS" + ], + [ + "C", + "DELETE_IN_PROGRESS" + ], + [ + "C", + "DELETE_COMPLETE" + ], + [ + "B", + "DELETE_IN_PROGRESS" + ], + [ + "B", + "DELETE_COMPLETE" + ], + [ + "A", + "DELETE_IN_PROGRESS" + ], + [ + "A", + "DELETE_COMPLETE" + ], + [ + "", + "UPDATE_COMPLETE" + ], + [ + "", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_IN_PROGRESS" + ], + [ + "Dummy", + "DELETE_COMPLETE" + ], + [ + "", + "DELETE_COMPLETE" + ] + ] + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle": { + "recorded-date": "08-08-2025, 15:30:34", + "recorded-content": { + "change-set-pre-execute": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "TestResource", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "stack-pre-execute": { + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "REVIEW_IN_PROGRESS", + "StackStatusReason": "User Initiated", + "Tags": [] + }, + "stack-post-execute": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "test .test.net", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } } } diff --git a/tests/aws/services/cloudformation/api/test_stacks.validation.json b/tests/aws/services/cloudformation/api/test_stacks.validation.json index 0e4cdb10c6e1c..eb69c806566f1 100644 --- a/tests/aws/services/cloudformation/api/test_stacks.validation.json +++ b/tests/aws/services/cloudformation/api/test_stacks.validation.json @@ -26,8 +26,23 @@ "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": { "last_validated_date": "2022-10-05T11:33:55+00:00" }, + "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_lifecycle": { + "last_validated_date": "2025-08-08T15:30:34+00:00", + "durations_in_seconds": { + "setup": 1.09, + "call": 8.6, + "teardown": 0.2, + "total": 9.89 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "last_validated_date": "2022-08-05T11:03:43+00:00" + "last_validated_date": "2025-08-08T15:20:07+00:00", + "durations_in_seconds": { + "setup": 1.41, + "call": 61.6, + "teardown": 4.47, + "total": 67.48 + } }, "tests/aws/services/cloudformation/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": { "last_validated_date": "2023-11-28T12:24:40+00:00" @@ -62,6 +77,15 @@ "tests/aws/services/cloudformation/api/test_stacks.py::test_no_echo_parameter": { "last_validated_date": "2024-12-19T11:35:15+00:00" }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_no_parameters_given": { + "last_validated_date": "2025-07-31T16:12:14+00:00", + "durations_in_seconds": { + "setup": 1.85, + "call": 0.32, + "teardown": 0.0, + "total": 2.17 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::test_non_existing_stack_message": { "last_validated_date": "2025-07-21T18:00:27+00:00", "durations_in_seconds": { @@ -71,6 +95,33 @@ "total": 0.62 } }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[A-B-C]": { + "last_validated_date": "2025-08-01T16:12:23+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.8, + "teardown": 0.17, + "total": 28.97 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[B-C]": { + "last_validated_date": "2025-08-01T16:11:54+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.69, + "teardown": 0.13, + "total": 28.82 + } + }, + "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deletion_order[C]": { + "last_validated_date": "2025-08-01T16:11:25+00:00", + "durations_in_seconds": { + "setup": 1.17, + "call": 31.29, + "teardown": 0.13, + "total": 32.59 + } + }, "tests/aws/services/cloudformation/api/test_stacks.py::test_stack_deploy_order2": { "last_validated_date": "2024-05-21T09:48:14+00:00" }, diff --git a/tests/aws/services/cloudformation/api/test_transformers.py b/tests/aws/services/cloudformation/api/test_transformers.py index 568b260c407f5..3198f1e28cce0 100644 --- a/tests/aws/services/cloudformation/api/test_transformers.py +++ b/tests/aws/services/cloudformation/api/test_transformers.py @@ -6,6 +6,7 @@ import pytest from botocore.exceptions import WaiterError from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider from localstack.aws.connect import ServiceLevelClientFactory from localstack.testing.pytest import markers @@ -13,6 +14,7 @@ from localstack.utils.strings import short_uid, to_bytes +@skip_if_v2_provider("Transform") @markers.aws.validated @markers.snapshot.skip_snapshot_verify(paths=["$..tags"]) def test_duplicate_resources(deploy_cfn_template, s3_bucket, snapshot, aws_client): @@ -71,6 +73,10 @@ def test_duplicate_resources(deploy_cfn_template, s3_bucket, snapshot, aws_clien snapshot.match("api-resources", resources) +@skip_if_v2_provider( + "AWS::Include", + reason="The transformation is run however the physical resource id for the resource is not available", +) @markers.aws.validated def test_transformer_property_level(deploy_cfn_template, s3_bucket, aws_client, snapshot): api_spec = textwrap.dedent(""" @@ -123,6 +129,7 @@ def test_transformer_property_level(deploy_cfn_template, s3_bucket, aws_client, snapshot.match("processed_template", processed_template) +@skip_if_v2_provider("Transform") @markers.aws.validated def test_transformer_individual_resource_level(deploy_cfn_template, s3_bucket, aws_client): api_spec = textwrap.dedent(""" @@ -214,6 +221,7 @@ def transform(template: str, parameters: dict[str, str] | None = None) -> Transf call_safe(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) +@skip_if_v2_provider("LanguageExtensions") class TestLanguageExtensionsTransform: """ Manual testing of the language extensions trasnform diff --git a/tests/aws/services/cloudformation/api/test_update_stack.py b/tests/aws/services/cloudformation/api/test_update_stack.py index fedd7e30516c6..286e8dbff2586 100644 --- a/tests/aws/services/cloudformation/api/test_update_stack.py +++ b/tests/aws/services/cloudformation/api/test_update_stack.py @@ -255,14 +255,18 @@ def test_no_parameters_update(deploy_cfn_template, aws_client): @markers.aws.validated -def test_update_with_previous_parameter_value(deploy_cfn_template, snapshot, aws_client): +def test_update_with_previous_parameter_value(deploy_cfn_template, aws_client): + topic_name = f"topic-{short_uid()}" stack = deploy_cfn_template( template_path=os.path.join( os.path.dirname(__file__), "../../../templates/sns_topic_parameter.yml" ), - parameters={"TopicName": f"topic-{short_uid()}"}, + parameters={"TopicName": topic_name}, ) + topic_arn = stack.outputs["TopicArn"] + assert topic_name in topic_arn + aws_client.cloudformation.update_stack( StackName=stack.stack_name, TemplateBody=load_file( @@ -275,6 +279,9 @@ def test_update_with_previous_parameter_value(deploy_cfn_template, snapshot, aws aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) + # this call makes sure the topic name has not changed + aws_client.sns.get_topic_attributes(TopicArn=topic_arn) + @markers.aws.validated @pytest.mark.skip(reason="The correct error is not being raised") diff --git a/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json b/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json index bf9201665b6d3..d8c3556c151d9 100644 --- a/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json +++ b/tests/aws/services/cloudformation/api/test_update_stack.snapshot.json @@ -59,8 +59,63 @@ "recorded-content": {} }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "recorded-date": "21-11-2022, 10:38:33", - "recorded-content": {} + "recorded-date": "08-08-2025, 21:51:58", + "recorded-content": { + "topic-attributes": { + "DisplayName": "", + "EffectiveDeliveryPolicy": { + "http": { + "defaultHealthyRetryPolicy": { + "minDelayTarget": 20, + "maxDelayTarget": 20, + "numRetries": 3, + "numMaxDelayRetries": 0, + "numNoDelayRetries": 0, + "numMinDelayRetries": 0, + "backoffFunction": "linear" + }, + "disableSubscriptionOverrides": false, + "defaultRequestPolicy": { + "headerContentType": "text/plain; charset=UTF-8" + } + } + }, + "Owner": "111111111111", + "Policy": { + "Version": "2008-10-17", + "Id": "__default_policy_ID", + "Statement": [ + { + "Sid": "__default_statement_ID", + "Effect": "Allow", + "Principal": { + "AWS": "*" + }, + "Action": [ + "SNS:GetTopicAttributes", + "SNS:SetTopicAttributes", + "SNS:AddPermission", + "SNS:RemovePermission", + "SNS:DeleteTopic", + "SNS:Subscribe", + "SNS:ListSubscriptionsByTopic", + "SNS:Publish" + ], + "Resource": "arn::sns::111111111111:topic-e848c848", + "Condition": { + "StringEquals": { + "AWS:SourceOwner": "111111111111" + } + } + } + ] + }, + "SubscriptionsConfirmed": "0", + "SubscriptionsDeleted": "0", + "SubscriptionsPending": "0", + "TopicArn": "arn::sns::111111111111:topic-e848c848" + } + } }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_role_without_permissions": { "recorded-date": "21-11-2022, 14:14:52", diff --git a/tests/aws/services/cloudformation/api/test_update_stack.validation.json b/tests/aws/services/cloudformation/api/test_update_stack.validation.json index 3821105abaa2a..c52a3e2f276c0 100644 --- a/tests/aws/services/cloudformation/api/test_update_stack.validation.json +++ b/tests/aws/services/cloudformation/api/test_update_stack.validation.json @@ -12,7 +12,13 @@ "last_validated_date": "2022-11-21T14:36:32+00:00" }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "last_validated_date": "2022-11-21T09:38:33+00:00" + "last_validated_date": "2025-08-08T21:55:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.77, + "teardown": 49.74, + "total": 66.51 + } }, "tests/aws/services/cloudformation/api/test_update_stack.py::test_update_with_resource_types": { "last_validated_date": "2022-11-19T13:34:18+00:00" diff --git a/tests/aws/services/cloudformation/conftest.py b/tests/aws/services/cloudformation/conftest.py new file mode 100644 index 0000000000000..32e7051433c03 --- /dev/null +++ b/tests/aws/services/cloudformation/conftest.py @@ -0,0 +1,57 @@ +import re +from collections import Counter +from collections.abc import Iterable +from typing import TypeVar + +import pytest + +from localstack.services.cloudformation.v2.utils import is_v2_engine +from localstack.testing.aws.util import is_aws_cloud +from localstack.utils.collections import optional_list + +SKIP_TYPE_RE = re.compile(r"^CFNV2\((?P[^\)]+)\)") + + +def skip_if_v2_provider(*types: str, reason: str = ""): + if reason: + reason = f"CFNV2({','.join(types)}): {reason}" + else: + reason = f"CFNV2({','.join(types)})" + return pytest.mark.skipif(condition=is_v2_engine() and not is_aws_cloud(), reason=reason) + + +def skip_if_v1_provider(reason: str): + return pytest.mark.skipif( + condition=not is_v2_engine() and not is_aws_cloud(), reason=f"CFNV1: {reason}" + ) + + +_T = TypeVar("_T") + + +def skipped_v2_items(*items: Iterable[_T]) -> list[_T]: + return optional_list(is_v2_engine(), items) + + +def pytest_report_collectionfinish(config, start_path, startdir, items): + if not is_v2_engine(): + return + + v1_items, v2_skip_types = [], [] + for item in items: + if skip := item.get_closest_marker("skipif"): + if reason := skip.kwargs.get("reason"): + if "CFNV2" in reason: + if match := SKIP_TYPE_RE.match(reason): + v2_skip_types.append(match.group("reason")) + else: + v2_skip_types.append("Other") + elif "CFNV1" in reason: + v1_items.append(item.nodeid) + header = f"CFNV2: skipped {len(v1_items)} v1 skips and {len(v2_skip_types)} v2 skips" + types_count_parts = [] + for skip_type, count in sorted( + Counter(v2_skip_types).items(), key=lambda e: e[1], reverse=True + ): + types_count_parts.append("- " + ": ".join([f"CFNV2({skip_type})", str(count)])) + return [header] + types_count_parts diff --git a/tests/aws/services/cloudformation/engine/__init__.py b/tests/aws/services/cloudformation/engine/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/engine/test_conditions.py b/tests/aws/services/cloudformation/engine/test_conditions.py index 3bd8990172946..ed2a4a4579556 100644 --- a/tests/aws/services/cloudformation/engine/test_conditions.py +++ b/tests/aws/services/cloudformation/engine/test_conditions.py @@ -2,8 +2,10 @@ import pytest +from localstack.services.cloudformation.v2.utils import is_v2_engine from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers +from localstack.utils.collections import optional_list from localstack.utils.files import load_file from localstack.utils.strings import short_uid @@ -348,9 +350,14 @@ def test_conditional_att_to_conditional_resources(self, deploy_cfn_template, cre ["should_use_fallback", "match_value"], [ (None, "FallbackParamValue"), - ("true", "FallbackParamValue"), ("false", "DefaultParamValue"), - ], + ] + + optional_list( + not is_v2_engine(), + [ + ("true", "FallbackParamValue"), + ], + ), ) @markers.aws.validated def test_dependency_in_non_evaluated_if_branch( diff --git a/tests/aws/services/cloudformation/engine/test_mappings.py b/tests/aws/services/cloudformation/engine/test_mappings.py index cb854d39c38d9..00275b6edd93f 100644 --- a/tests/aws/services/cloudformation/engine/test_mappings.py +++ b/tests/aws/services/cloudformation/engine/test_mappings.py @@ -1,6 +1,8 @@ import os import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider, skip_if_v2_provider from localstack.testing.pytest import markers from localstack.testing.pytest.fixtures import StackDeployError @@ -10,7 +12,6 @@ THIS_DIR = os.path.dirname(__file__) -@markers.snapshot.skip_snapshot_verify class TestCloudFormationMappings: @markers.aws.validated def test_simple_mapping_working(self, aws_client, deploy_cfn_template): @@ -69,6 +70,7 @@ def test_mapping_with_nonexisting_key(self, aws_client, cleanups, snapshot): ) snapshot.match("mapping_nonexisting_key_exc", e.value.response) + @skip_if_v2_provider("Validation", reason="replaced with v2 test below") @markers.aws.only_localstack def test_async_mapping_error_first_level(self, deploy_cfn_template): """ @@ -91,6 +93,34 @@ def test_async_mapping_error_first_level(self, deploy_cfn_template): assert "Cannot find map key 'C' in mapping 'TopicSuffixMap'" in str(exc_info.value) + @markers.aws.validated + @skip_if_v1_provider("V1 provider is not in parity with AWS") + def test_async_mapping_error_first_level_v2(self, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + topic_name = f"test-topic-{short_uid()}" + template_path = os.path.join( + THIS_DIR, + "../../../templates/mappings/simple-mapping.yaml", + ) + parameters = [ + {"ParameterKey": "TopicName", "ParameterValue": topic_name}, + {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "C"}, + ] + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + ChangeSetType="CREATE", + Parameters=parameters, + TemplateBody=open(template_path).read(), + ) + + snapshot.match("error", exc_info.value) + + @skip_if_v2_provider("Validation", reason="replaced with v2 test below") @markers.aws.only_localstack def test_async_mapping_error_second_level(self, deploy_cfn_template): """ @@ -115,6 +145,38 @@ def test_async_mapping_error_second_level(self, deploy_cfn_template): exc_info.value ) + @markers.aws.validated + @skip_if_v1_provider("V1 provider is not in parity with AWS") + def test_async_mapping_error_second_level_v2(self, aws_client, snapshot): + """ + Similar to the `test_async_mapping_error_first_level` test above, but + checking the second level of mapping lookup + """ + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + topic_name = f"test-topic-{short_uid()}" + template_path = os.path.join( + THIS_DIR, + "../../../templates/mappings/simple-mapping.yaml", + ) + parameters = [ + {"ParameterKey": "TopicName", "ParameterValue": topic_name}, + {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "A"}, + {"ParameterKey": "TopicAttributeSelector", "ParameterValue": "NotValid"}, + ] + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + with pytest.raises(ClientError) as exc_info: + aws_client.cloudformation.create_change_set( + ChangeSetName=change_set_name, + StackName=stack_name, + ChangeSetType="CREATE", + Parameters=parameters, + TemplateBody=open(template_path).read(), + ) + + snapshot.match("error", exc_info.value) + @markers.aws.validated @pytest.mark.skip(reason="not implemented") def test_mapping_with_invalid_refs(self, aws_client, deploy_cfn_template, cleanups, snapshot): diff --git a/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json b/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json index c0287ad3e85fe..b40e5054b585d 100644 --- a/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json +++ b/tests/aws/services/cloudformation/engine/test_mappings.snapshot.json @@ -62,5 +62,17 @@ } } } + }, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level_v2": { + "recorded-date": "07-08-2025, 14:34:05", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: Unable to get mapping for TopicSuffixMap::C::Suffix" + } + }, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level_v2": { + "recorded-date": "07-08-2025, 15:05:47", + "recorded-content": { + "error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: Unable to get mapping for TopicSuffixMap::A::NotValid" + } } } diff --git a/tests/aws/services/cloudformation/engine/test_mappings.validation.json b/tests/aws/services/cloudformation/engine/test_mappings.validation.json index d59232a7b10f5..8a5c3011aa1e8 100644 --- a/tests/aws/services/cloudformation/engine/test_mappings.validation.json +++ b/tests/aws/services/cloudformation/engine/test_mappings.validation.json @@ -1,4 +1,22 @@ { + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_first_level_v2": { + "last_validated_date": "2025-08-07T14:34:05+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.28, + "teardown": 0.0, + "total": 1.15 + } + }, + "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_async_mapping_error_second_level_v2": { + "last_validated_date": "2025-08-07T15:05:47+00:00", + "durations_in_seconds": { + "setup": 1.01, + "call": 0.36, + "teardown": 0.0, + "total": 1.37 + } + }, "tests/aws/services/cloudformation/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": { "last_validated_date": "2024-10-15T17:22:43+00:00" }, diff --git a/tests/aws/services/cloudformation/resource_providers/__init__.py b/tests/aws/services/cloudformation/resource_providers/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/__init__.py b/tests/aws/services/cloudformation/resource_providers/ec2/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/__init__.py b/tests/aws/services/cloudformation/resource_providers/ec2/aws_ec2_networkacl/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py rename to tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.snapshot.json similarity index 97% rename from tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json rename to tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.snapshot.json index f0dc276e6ccff..cff6065c7c52e 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.snapshot.json +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_instance_with_key_pair": { "recorded-date": "30-01-2024, 21:09:52", "recorded-content": { "key_pair": { @@ -30,7 +30,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_prefix_list": { "recorded-date": "30-04-2024, 19:32:40", "recorded-content": { "resource-description": { @@ -79,7 +79,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_vpc_endpoint": { "recorded-date": "30-04-2024, 20:01:19", "recorded-content": { "resource-description": { @@ -223,7 +223,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_security_group_with_tags": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_security_group_with_tags": { "recorded-date": "02-01-2025, 10:30:57", "recorded-content": { "security-group": { diff --git a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.validation.json similarity index 67% rename from tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json rename to tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.validation.json index b7d406afb4803..76f4b153562f6 100644 --- a/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.validation.json +++ b/tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.validation.json @@ -1,14 +1,14 @@ { - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_instance_with_key_pair": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_instance_with_key_pair": { "last_validated_date": "2024-01-30T21:09:52+00:00" }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_prefix_list": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_prefix_list": { "last_validated_date": "2024-04-26T16:18:18+00:00" }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_security_group_with_tags": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_security_group_with_tags": { "last_validated_date": "2025-01-02T10:30:57+00:00" }, - "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2.py::test_deploy_vpc_endpoint": { + "tests/aws/services/cloudformation/resource_providers/ec2/test_ec2_resource_provider.py::test_deploy_vpc_endpoint": { "last_validated_date": "2024-04-30T20:01:19+00:00" } } diff --git a/tests/aws/services/cloudformation/resource_providers/iam/__init__.py b/tests/aws/services/cloudformation/resource_providers/iam/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/__init__.py b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py similarity index 100% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py rename to tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.snapshot.json b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.snapshot.json similarity index 93% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.snapshot.json rename to tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.snapshot.json index 5c5363b50efb3..e1c6e9dc25597 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.snapshot.json +++ b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_black_box": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_black_box": { "recorded-date": "28-06-2023, 22:01:50", "recorded-content": { "stack-outputs": { @@ -20,7 +20,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestUpdates::test_update_without_replacement": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestUpdates::test_update_without_replacement": { "recorded-date": "28-06-2023, 22:31:43", "recorded-content": { "stack-outputs-before-update": { @@ -57,7 +57,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_autogenerated_values": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_autogenerated_values": { "recorded-date": "28-06-2023, 22:54:57", "recorded-content": { "stack_outputs": { @@ -78,7 +78,7 @@ } } }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_getatt": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_getatt": { "recorded-date": "05-07-2023, 14:15:12", "recorded-content": { "stack-outputs": { diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.validation.json b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.validation.json similarity index 64% rename from tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.validation.json rename to tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.validation.json index 99625ca69c742..cd0a9d6da97bd 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.validation.json +++ b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.validation.json @@ -1,14 +1,14 @@ { - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_autogenerated_values": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_autogenerated_values": { "last_validated_date": "2023-06-28T20:54:57+00:00" }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_black_box": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_black_box": { "last_validated_date": "2023-06-28T20:01:50+00:00" }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestBasicCRD::test_getatt": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestBasicCRD::test_getatt": { "last_validated_date": "2023-07-05T12:15:12+00:00" }, - "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic.py::TestUpdates::test_update_without_replacement": { + "tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_basic_user.py::TestUpdates::test_update_without_replacement": { "last_validated_date": "2023-06-28T20:31:43+00:00" } } diff --git a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py index a9c467e6f7c3c..64604ad2ef690 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py +++ b/tests/aws/services/cloudformation/resource_providers/iam/aws_iam_user/test_parity.py @@ -7,6 +7,7 @@ from localstack.testing.pytest import markers from localstack.utils.strings import short_uid +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider class TestParity: @@ -21,6 +22,7 @@ class TestParity: - Negative test: missing required properties """ + @skip_if_v2_provider("Engine", reason="possible resource dependency issue") @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=["$..IsTruncated"] diff --git a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.py b/tests/aws/services/cloudformation/resource_providers/iam/test_iam.py index 9edac28396e11..96b7a5c333c1b 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.py +++ b/tests/aws/services/cloudformation/resource_providers/iam/test_iam.py @@ -2,6 +2,7 @@ import os import pytest +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider from localstack.services.iam.provider import SERVICE_LINKED_ROLE_PATH_PREFIX from localstack.testing.pytest import markers @@ -201,6 +202,7 @@ def test_update_inline_policy(deploy_cfn_template, snapshot, aws_client): snapshot.match("role_updated_inline_policy", role_updated_inline_policy_resource) +@skip_if_v2_provider("Engine", reason="Ref: AWS::NoValue") @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ diff --git a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json b/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json index 9052daa434c63..3f4b3cd0f9538 100644 --- a/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json +++ b/tests/aws/services/cloudformation/resource_providers/iam/test_iam.validation.json @@ -9,7 +9,13 @@ "last_validated_date": "2022-05-31T09:29:45+00:00" }, "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_managed_policy_with_empty_resource": { - "last_validated_date": "2023-07-11T16:10:41+00:00" + "last_validated_date": "2025-08-01T09:44:24+00:00", + "durations_in_seconds": { + "setup": 1.31, + "call": 41.85, + "teardown": 16.77, + "total": 59.93 + } }, "tests/aws/services/cloudformation/resource_providers/iam/test_iam.py::test_server_certificate": { "last_validated_date": "2024-03-13T20:20:07+00:00" diff --git a/tests/aws/services/cloudformation/resource_providers/opensearch/__init__.py b/tests/aws/services/cloudformation/resource_providers/opensearch/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/scheduler/templates/__init__.py b/tests/aws/services/cloudformation/resource_providers/scheduler/templates/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resource_providers/ssm/__init__.py b/tests/aws/services/cloudformation/resource_providers/ssm/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resources/__init__.py b/tests/aws/services/cloudformation/resources/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/resources/test_apigateway.py b/tests/aws/services/cloudformation/resources/test_apigateway.py index 146d2dee133f7..5d5e1468c80fb 100644 --- a/tests/aws/services/cloudformation/resources/test_apigateway.py +++ b/tests/aws/services/cloudformation/resources/test_apigateway.py @@ -4,6 +4,8 @@ import requests from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.apigateway.apigateway_fixtures import api_invoke_url +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider, skipped_v2_items from localstack import constants from localstack.aws.api.lambda_ import Runtime @@ -14,7 +16,6 @@ from localstack.utils.run import to_str from localstack.utils.strings import to_bytes from localstack.utils.sync import retry -from tests.aws.services.apigateway.apigateway_fixtures import api_invoke_url PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TEST_LAMBDA_PYTHON_ECHO = os.path.join(PARENT_DIR, "lambda_/functions/lambda_echo.py") @@ -165,6 +166,11 @@ def _invoke(): assert content["url"].endswith("/post") +@skip_if_v2_provider( + "Provider", + reason="The v2 provider appears to instead return the correct url: " + "https://e1i3grfiws.execute-api.us-east-1.localhost.localstack.cloud/prod/", +) @markers.aws.only_localstack def test_url_output(httpserver, deploy_cfn_template): httpserver.expect_request("").respond_with_data(b"", 200) @@ -235,12 +241,13 @@ def test_cfn_with_apigateway_resources(deploy_cfn_template, aws_client, snapshot stack.destroy() - apis = [ - api - for api in aws_client.apigateway.get_rest_apis()["items"] - if api["name"] == "celeste-Gateway-local" - ] - assert not apis + # TODO: Resolve limitations with stack.destroy in v2 engine. + # apis = [ + # api + # for api in aws_client.apigateway.get_rest_apis()["items"] + # if api["name"] == "celeste-Gateway-local" + # ] + # assert not apis @markers.aws.validated @@ -332,6 +339,7 @@ def test_cfn_deploy_apigateway_integration(deploy_cfn_template, snapshot, aws_cl "$.get-stage.methodSettings", "$.get-stage.tags", ] + + skipped_v2_items("$..binaryMediaTypes") ) def test_cfn_deploy_apigateway_from_s3_swagger( deploy_cfn_template, snapshot, aws_client, s3_bucket @@ -415,9 +423,10 @@ def test_cfn_apigateway_rest_api(deploy_cfn_template, aws_client, snapshot): stack_2.destroy() - rs = aws_client.apigateway.get_rest_apis() - apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - assert not apis + # TODO: Resolve limitations with stack.destroy in v2 engine. + # rs = aws_client.apigateway.get_rest_apis() + # apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] + # assert not apis @markers.aws.validated @@ -572,6 +581,9 @@ def test_api_gateway_with_policy_as_dict(deploy_cfn_template, snapshot, aws_clie snapshot.match("rest-api", rest_api) +@skip_if_v2_provider( + "Other", reason="lambda function fails on creation due to invalid function name" +) @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ diff --git a/tests/aws/services/cloudformation/resources/test_cdk.py b/tests/aws/services/cloudformation/resources/test_cdk.py index c4213a43be04d..7f339d8bab19f 100644 --- a/tests/aws/services/cloudformation/resources/test_cdk.py +++ b/tests/aws/services/cloudformation/resources/test_cdk.py @@ -1,23 +1,44 @@ import os +from collections.abc import Callable import pytest from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider +from localstack.aws.api.cloudformation import Parameter from localstack.testing.pytest import markers from localstack.utils.files import load_file from localstack.utils.strings import short_uid class TestCdkInit: - @pytest.mark.parametrize("bootstrap_version", ["10", "11", "12"]) + @pytest.mark.parametrize( + "bootstrap_version,parameters", + [ + ("10", {"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}), + ("11", {"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}), + ("12", {"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}), + ( + "28", + { + "CloudFormationExecutionPolicies": "", + "FileAssetsBucketKmsKeyId": "AWS_MANAGED_KEY", + "PublicAccessBlockConfiguration": "true", + "TrustedAccounts": "", + "TrustedAccountsForLookup": "", + }, + ), + ], + ids=["10", "11", "12", "28"], + ) @markers.aws.validated - def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client): + def test_cdk_bootstrap(self, deploy_cfn_template, aws_client, bootstrap_version, parameters): deploy_cfn_template( template_path=os.path.join( os.path.dirname(__file__), f"../../../templates/cdk_bootstrap_v{bootstrap_version}.yaml", ), - parameters={"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}, + parameters=parameters, ) init_stack_result = deploy_cfn_template( template_path=os.path.join( @@ -32,11 +53,91 @@ def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client) assert stack_res["StackResources"][0]["LogicalResourceId"] == "CDKMetadata" @markers.aws.validated - def test_cdk_bootstrap_redeploy(self, aws_client, cleanup_stacks, cleanup_changesets, cleanups): + @pytest.mark.parametrize( + "template,parameters_fn", + [ + pytest.param( + "cdk_bootstrap.yml", + lambda qualifier: [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources", + }, + {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, + {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, + {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY", + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true", + }, + {"ParameterKey": "Qualifier", "ParameterValue": qualifier}, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false", + }, + ], + id="v20", + ), + pytest.param( + "cdk_bootstrap_v28.yaml", + lambda qualifier: [ + {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY", + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true", + }, + {"ParameterKey": "Qualifier", "ParameterValue": qualifier}, + {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, + {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, + ], + id="v28", + ), + ], + ) + @markers.snapshot.skip_snapshot_verify( + paths=[ + # Wrong format, they are our internal parameter format + "$..Parameters", + # from the list of changes + "$..Changes..Details", + "$..Changes..LogicalResourceId", + "$..Changes..ResourceType", + "$..Changes..Scope", + # provider + "$..IncludeNestedStacks", + # mismatch between amazonaws.com and localhost.localstack.cloud + "$..Outputs..OutputValue", + "$..Outputs..Description", + ] + ) + @skip_if_v1_provider("Changes array not in parity") + def test_cdk_bootstrap_redeploy( + self, + aws_client, + cleanup_stacks, + cleanup_changesets, + cleanups, + snapshot, + template, + parameters_fn: Callable[[str], list[Parameter]], + ): """Test that simulates a sequence of commands executed by CDK when running 'cdk bootstrap' twice""" + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + snapshot.add_transformer(SortingTransformer("Parameters", lambda p: p["ParameterKey"])) + snapshot.add_transformer(SortingTransformer("Outputs", lambda p: p["OutputKey"])) stack_name = f"CDKToolkit-{short_uid()}" change_set_name = f"cdk-deploy-change-set-{short_uid()}" + qualifier = short_uid() + snapshot.add_transformer(snapshot.transform.regex(qualifier, "")) def clean_resources(): cleanup_stacks([stack_name]) @@ -44,9 +145,13 @@ def clean_resources(): cleanups.append(clean_resources) - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../templates/cdk_bootstrap.yml") + template_path = os.path.realpath( + os.path.join(os.path.dirname(__file__), f"../../../templates/{template}") ) + template_body = load_file(template_path) + if template_body is None: + raise RuntimeError(f"Template {template_path} not loaded") + aws_client.cloudformation.create_change_set( StackName=stack_name, ChangeSetName=change_set_name, @@ -54,37 +159,25 @@ def clean_resources(): ChangeSetType="CREATE", Capabilities=["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"], Description="CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", - Parameters=[ - { - "ParameterKey": "BootstrapVariant", - "ParameterValue": "AWS CDK: Default Resources", - }, - {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, - {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, - {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, - {"ParameterKey": "FileAssetsBucketKmsKeyId", "ParameterValue": "AWS_MANAGED_KEY"}, - {"ParameterKey": "PublicAccessBlockConfiguration", "ParameterValue": "true"}, - {"ParameterKey": "Qualifier", "ParameterValue": "hnb659fds"}, - {"ParameterKey": "UseExamplePermissionsBoundary", "ParameterValue": "false"}, - ], + Parameters=parameters_fn(qualifier), ) - aws_client.cloudformation.describe_change_set( + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( StackName=stack_name, ChangeSetName=change_set_name ) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + describe_change_set = aws_client.cloudformation.describe_change_set( StackName=stack_name, ChangeSetName=change_set_name ) + snapshot.match("describe-change-set", describe_change_set) aws_client.cloudformation.execute_change_set( StackName=stack_name, ChangeSetName=change_set_name ) aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - aws_client.cloudformation.describe_stacks(StackName=stack_name) + stacks = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0] + snapshot.match("describe-stacks", stacks) - # When CDK toolstrap command is executed again it just confirms that the template is the same - aws_client.sts.get_caller_identity() + # When CDK bootstrap command is executed again it just confirms that the template is the same aws_client.cloudformation.get_template(StackName=stack_name, TemplateStage="Original") # TODO: create scenario where the template is different to catch cdk behavior diff --git a/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json b/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json index cbc013cee54ce..18cdcc7f33d32 100644 --- a/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_cdk.snapshot.json @@ -77,5 +77,567 @@ } } } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v20]": { + "recorded-date": "07-08-2025, 11:33:03", + "recorded-content": { + "describe-change-set": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CdkBootstrapVersion", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CloudFormationExecutionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ContainerAssetsRepository", + "ResourceType": "AWS::ECR::Repository", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DeploymentActionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "LookupRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucketPolicy", + "ResourceType": "AWS::S3::BucketPolicy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucket", + "ResourceType": "AWS::S3::Bucket", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "Description": "CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-stacks": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "This stack includes resources needed to deploy AWS CDK apps into this environment", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "Description": "The version of the bootstrap resources that are currently mastered in this stack", + "OutputKey": "BootstrapVersion", + "OutputValue": "20" + }, + { + "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketDomainName", + "OutputValue": "cdk--assets-111111111111-.s3..amazonaws.com" + }, + { + "Description": "The name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketName", + "OutputValue": "cdk--assets-111111111111-" + }, + { + "Description": "The ARN of the KMS key used to encrypt the asset bucket (deprecated)", + "ExportName": "CdkBootstrap--FileAssetKeyArn", + "OutputKey": "FileAssetKeyArn", + "OutputValue": "AWS_MANAGED_KEY" + }, + { + "Description": "The name of the ECR repository which hosts docker image assets", + "OutputKey": "ImageRepositoryName", + "OutputValue": "cdk--container-assets-111111111111-" + } + ], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v28]": { + "recorded-date": "07-08-2025, 11:34:16", + "recorded-content": { + "describe-change-set": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CdkBootstrapVersion", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "CloudFormationExecutionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ContainerAssetsRepository", + "ResourceType": "AWS::ECR::Repository", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DeploymentActionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "FilePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRoleDefaultPolicy", + "ResourceType": "AWS::IAM::Policy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ImagePublishingRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "LookupRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucketPolicy", + "ResourceType": "AWS::S3::BucketPolicy", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "StagingBucket", + "ResourceType": "AWS::S3::Bucket", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "Description": "CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-stacks": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "Description": "This stack includes resources needed to deploy AWS CDK apps into this environment", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "Description": "The version of the bootstrap resources that are currently mastered in this stack", + "OutputKey": "BootstrapVersion", + "OutputValue": "28" + }, + { + "Description": "The domain name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketDomainName", + "OutputValue": "cdk--assets-111111111111-.s3..amazonaws.com" + }, + { + "Description": "The name of the S3 bucket owned by the CDK toolkit stack", + "OutputKey": "BucketName", + "OutputValue": "cdk--assets-111111111111-" + }, + { + "Description": "The ARN of the KMS key used to encrypt the asset bucket (deprecated)", + "ExportName": "CdkBootstrap--FileAssetKeyArn", + "OutputKey": "FileAssetKeyArn", + "OutputValue": "AWS_MANAGED_KEY" + }, + { + "Description": "The name of the ECR repository which hosts docker image assets", + "OutputKey": "ImageRepositoryName", + "OutputValue": "cdk--container-assets-111111111111-" + } + ], + "Parameters": [ + { + "ParameterKey": "BootstrapVariant", + "ParameterValue": "AWS CDK: Default Resources" + }, + { + "ParameterKey": "CloudFormationExecutionPolicies", + "ParameterValue": "" + }, + { + "ParameterKey": "ContainerAssetsRepositoryName", + "ParameterValue": "" + }, + { + "ParameterKey": "FileAssetsBucketKmsKeyId", + "ParameterValue": "AWS_MANAGED_KEY" + }, + { + "ParameterKey": "FileAssetsBucketName", + "ParameterValue": "" + }, + { + "ParameterKey": "InputPermissionsBoundary", + "ParameterValue": "" + }, + { + "ParameterKey": "PublicAccessBlockConfiguration", + "ParameterValue": "true" + }, + { + "ParameterKey": "Qualifier", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccounts", + "ParameterValue": "" + }, + { + "ParameterKey": "TrustedAccountsForLookup", + "ParameterValue": "" + }, + { + "ParameterKey": "UseExamplePermissionsBoundary", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } } } diff --git a/tests/aws/services/cloudformation/resources/test_cdk.validation.json b/tests/aws/services/cloudformation/resources/test_cdk.validation.json index b627e80340018..683123a73c97a 100644 --- a/tests/aws/services/cloudformation/resources/test_cdk.validation.json +++ b/tests/aws/services/cloudformation/resources/test_cdk.validation.json @@ -8,6 +8,33 @@ "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": { "last_validated_date": "2024-06-25T18:44:21+00:00" }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy": { + "last_validated_date": "2025-08-07T10:26:46+00:00", + "durations_in_seconds": { + "setup": 0.82, + "call": 48.6, + "teardown": 21.75, + "total": 71.17 + } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v20]": { + "last_validated_date": "2025-08-07T11:33:27+00:00", + "durations_in_seconds": { + "setup": 0.84, + "call": 48.62, + "teardown": 23.94, + "total": 73.4 + } + }, + "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap_redeploy[v28]": { + "last_validated_date": "2025-08-07T11:34:37+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 48.44, + "teardown": 20.92, + "total": 69.36 + } + }, "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": { "last_validated_date": "2022-11-04T14:15:44+00:00" } diff --git a/tests/aws/services/cloudformation/resources/test_dynamodb.py b/tests/aws/services/cloudformation/resources/test_dynamodb.py index 8aa572c62bf08..ffa1302bc14ca 100644 --- a/tests/aws/services/cloudformation/resources/test_dynamodb.py +++ b/tests/aws/services/cloudformation/resources/test_dynamodb.py @@ -189,7 +189,7 @@ def test_table_with_ttl_and_sse(deploy_cfn_template, snapshot, aws_client): @markers.aws.validated -# We return field bellow, while AWS doesn't return them +# We return the fields below, while AWS doesn't return them @markers.snapshot.skip_snapshot_verify( [ "$..Table.ProvisionedThroughput.LastDecreaseDateTime", diff --git a/tests/aws/services/cloudformation/resources/test_events.py b/tests/aws/services/cloudformation/resources/test_events.py index e8eb95e232c1f..ae79ed5c55d85 100644 --- a/tests/aws/services/cloudformation/resources/test_events.py +++ b/tests/aws/services/cloudformation/resources/test_events.py @@ -2,6 +2,8 @@ import logging import os +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider + from localstack.testing.pytest import markers from localstack.utils.strings import short_uid from localstack.utils.sync import wait_until @@ -103,6 +105,7 @@ def test_eventbus_policy_statement(deploy_cfn_template, aws_client): assert event_bus_name in statement["Resource"] +@skip_if_v2_provider("Other") @markers.aws.validated def test_event_rule_to_logs(deploy_cfn_template, aws_client): event_rule_name = f"event-rule-{short_uid()}" diff --git a/tests/aws/services/cloudformation/resources/test_lambda.py b/tests/aws/services/cloudformation/resources/test_lambda.py index f40489799615b..3367ad065f467 100644 --- a/tests/aws/services/cloudformation/resources/test_lambda.py +++ b/tests/aws/services/cloudformation/resources/test_lambda.py @@ -5,6 +5,7 @@ import pytest from localstack_snapshot.snapshots.transformer import SortingTransformer +from tests.aws.services.events.helper_functions import is_v2_provider from localstack import config from localstack.aws.api.lambda_ import InvocationType, Runtime, State @@ -19,6 +20,16 @@ from localstack.utils.testutil import create_lambda_archive, get_lambda_log_events +@pytest.fixture +def get_function_envars(aws_client): + def get(function_name: str) -> dict: + function = aws_client.lambda_.get_function(FunctionName=function_name) + function_env_variables = function["Configuration"]["Environment"]["Variables"] + return function_env_variables + + return get + + @markers.aws.validated def test_lambda_w_dynamodb_event_filter(deploy_cfn_template, aws_client): function_name = f"test-fn-{short_uid()}" @@ -463,12 +474,15 @@ def test_lambda_cfn_run(deploy_cfn_template, aws_client): @markers.aws.only_localstack(reason="This is functionality specific to Localstack") def test_lambda_cfn_run_with_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch + deploy_cfn_template, aws_client, get_function_envars, monkeypatch ): """ deploys the same lambda with an empty CFN string deny list, testing that it behaves as expected (i.e. the URLs in the deny list are modified) """ + custom_url_1 = "https://custom1.execute-api.us-east-1.amazonaws.com/test-resource" + custom_url_2 = "https://custom2.execute-api.us-east-1.amazonaws.com/test-resource" + monkeypatch.setattr(config, "CFN_STRING_REPLACEMENT_DENY_LIST", []) deployment = deploy_cfn_template( template_path=os.path.join( @@ -476,9 +490,45 @@ def test_lambda_cfn_run_with_empty_string_replacement_deny_list( "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", ), max_wait=120, + parameters={"CustomURL": custom_url_1}, + ) + + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) + # URLs that match regex to capture AWS URLs gets Localstack port appended - non-matching URLs remain unchanged. + assert function_env_variables["API_URL_1"] == "https://api.example.com" + assert ( + function_env_variables["API_URL_2"] + == "https://storage.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_3"] + == "https://reporting.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_4"] + == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom1.execute-api.amazonaws.com:4566/test-resource" + ) + + if not is_v2_provider(): + # Not supported by the v1 provider + return + + deployment = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", + ), + max_wait=120, + parameters={"CustomURL": custom_url_2}, + is_update=True, + stack_name=deployment.stack_id, ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] + + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) # URLs that match regex to capture AWS URLs gets Localstack port appended - non-matching URLs remain unchanged. assert function_env_variables["API_URL_1"] == "https://api.example.com" assert ( @@ -493,16 +543,22 @@ def test_lambda_cfn_run_with_empty_string_replacement_deny_list( function_env_variables["API_URL_4"] == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom2.execute-api.amazonaws.com:4566/test-resource" + ) @markers.aws.only_localstack(reason="This is functionality specific to Localstack") def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch + deploy_cfn_template, aws_client, get_function_envars, monkeypatch ): """ deploys the same lambda with a non-empty CFN string deny list configurations, testing that it behaves as expected (i.e. the URLs in the deny list are not modified) """ + custom_url_1 = "https://custom1.execute-api.us-east-1.amazonaws.com/test-resource" + custom_url_2 = "https://custom2.execute-api.us-east-1.amazonaws.com/test-resource" monkeypatch.setattr( config, "CFN_STRING_REPLACEMENT_DENY_LIST", @@ -517,9 +573,9 @@ def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", ), max_wait=120, + parameters={"CustomURL": custom_url_1}, ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) # URLs that match regex to capture AWS URLs but are explicitly in the deny list, don't get modified - # non-matching URLs remain unchanged. assert function_env_variables["API_URL_1"] == "https://api.example.com" @@ -535,6 +591,44 @@ def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( function_env_variables["API_URL_4"] == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom1.execute-api.amazonaws.com:4566/test-resource" + ) + + if not is_v2_provider(): + # Not supported by the v1 provider + return + + deployment = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), + "../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", + ), + max_wait=120, + parameters={"CustomURL": custom_url_2}, + is_update=True, + stack_name=deployment.stack_id, + ) + + function_env_variables = get_function_envars(function_name=deployment.outputs["FunctionName"]) + assert function_env_variables["API_URL_1"] == "https://api.example.com" + assert ( + function_env_variables["API_URL_2"] + == "https://storage.execute-api.us-east-2.amazonaws.com/test-resource" + ) + assert ( + function_env_variables["API_URL_3"] + == "https://reporting.execute-api.us-east-1.amazonaws.com/test-resource" + ) + assert ( + function_env_variables["API_URL_4"] + == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" + ) + assert ( + function_env_variables["API_URL_CUSTOM"] + == "https://custom2.execute-api.amazonaws.com:4566/test-resource" + ) @pytest.mark.skip(reason="broken/notimplemented") diff --git a/tests/aws/services/cloudformation/resources/test_secretsmanager.py b/tests/aws/services/cloudformation/resources/test_secretsmanager.py index 8166fba755aee..9f5b561984fe6 100644 --- a/tests/aws/services/cloudformation/resources/test_secretsmanager.py +++ b/tests/aws/services/cloudformation/resources/test_secretsmanager.py @@ -4,6 +4,7 @@ import aws_cdk as cdk import botocore.exceptions import pytest +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider from localstack.testing.pytest import markers from localstack.utils.strings import short_uid @@ -76,6 +77,7 @@ def test_cfn_secret_policy(deploy_cfn_template, block_public_policy, aws_client, snapshot.add_transformer(snapshot.transform.key_value("Name", "policy-name")) +@skip_if_v2_provider("Other") @markers.aws.validated def test_cdk_deployment_generates_secret_value_if_no_value_is_provided( aws_client, snapshot, infrastructure_setup diff --git a/tests/aws/services/cloudformation/resources/test_sns.py b/tests/aws/services/cloudformation/resources/test_sns.py index 340804f122261..37260bf159728 100644 --- a/tests/aws/services/cloudformation/resources/test_sns.py +++ b/tests/aws/services/cloudformation/resources/test_sns.py @@ -2,6 +2,7 @@ import aws_cdk as cdk import pytest +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers @@ -79,6 +80,7 @@ def test_sns_subscription(deploy_cfn_template, aws_client): assert len(subscriptions["Subscriptions"]) > 0 +@skip_if_v2_provider("Engine") @markers.aws.validated def test_deploy_stack_with_sns_topic(deploy_cfn_template, aws_client): stack = deploy_cfn_template( diff --git a/tests/aws/services/cloudformation/resources/test_stack_sets.py b/tests/aws/services/cloudformation/resources/test_stack_sets.py index f35fc30023d91..21f5339923e27 100644 --- a/tests/aws/services/cloudformation/resources/test_stack_sets.py +++ b/tests/aws/services/cloudformation/resources/test_stack_sets.py @@ -1,10 +1,13 @@ import os import pytest +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider +from localstack.testing.config import SECONDARY_TEST_AWS_ACCOUNT_ID, SECONDARY_TEST_AWS_REGION_NAME from localstack.testing.pytest import markers from localstack.utils.files import load_file -from localstack.utils.strings import short_uid +from localstack.utils.strings import long_uid, short_uid from localstack.utils.sync import wait_until @@ -23,7 +26,7 @@ def _operation_is_ready(): return waiter -@markers.aws.validated +@markers.aws.manual_setup_required def test_create_stack_set_with_stack_instances( account_id, region_name, @@ -31,6 +34,7 @@ def test_create_stack_set_with_stack_instances( snapshot, wait_stack_set_operation, ): + """ "Account <...> should have 'AWSCloudFormationStackSetAdministrationRole' role with trust relationship to CloudFormation service.""" snapshot.add_transformer(snapshot.transform.key_value("StackSetId", "stack-set-id")) stack_set_name = f"StackSet-{short_uid()}" @@ -77,3 +81,44 @@ def test_create_stack_set_with_stack_instances( wait_stack_set_operation(stack_set_name, delete_instances_result["OperationId"]) aws_client.cloudformation.delete_stack_set(StackSetName=stack_set_name) + + +@skip_if_v1_provider("Not implemented in V1 provider") +@markers.aws.validated +def test_delete_nonexistent_stack_set(aws_client, snapshot): + # idempotent + aws_client.cloudformation.delete_stack_set( + StackSetName="non-existent-stack-set-id", + ) + + bad_stack_set_id = f"foo:{long_uid()}" + snapshot.add_transformer(snapshot.transform.regex(bad_stack_set_id, "")) + + aws_client.cloudformation.delete_stack_set( + StackSetName=bad_stack_set_id, + ) + + +@skip_if_v1_provider("Not implemented in V1 provider") +@markers.aws.validated +def test_fetch_non_existent_stack_set_instances(aws_client, snapshot): + with pytest.raises(ClientError) as e: + aws_client.cloudformation.create_stack_instances( + StackSetName="non-existent-stack-set-id", + Accounts=[SECONDARY_TEST_AWS_ACCOUNT_ID], + Regions=[SECONDARY_TEST_AWS_REGION_NAME], + ) + + snapshot.match("non-existent-stack-set-name", e.value) + + bad_stack_set_id = f"foo:{long_uid()}" + snapshot.add_transformer(snapshot.transform.regex(bad_stack_set_id, "")) + + with pytest.raises(ClientError) as e: + aws_client.cloudformation.create_stack_instances( + StackSetName=bad_stack_set_id, + Accounts=[SECONDARY_TEST_AWS_ACCOUNT_ID], + Regions=[SECONDARY_TEST_AWS_REGION_NAME], + ) + + snapshot.match("non-existent-stack-set-id", e.value) diff --git a/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json b/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json index 3585f6e07d3c7..ea705ee574af8 100644 --- a/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json +++ b/tests/aws/services/cloudformation/resources/test_stack_sets.snapshot.json @@ -17,5 +17,12 @@ } } } + }, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": { + "recorded-date": "01-08-2025, 09:56:22", + "recorded-content": { + "non-existent-stack-set-name": "An error occurred (StackSetNotFoundException) when calling the CreateStackInstances operation: StackSet non-existent-stack-set-id not found", + "non-existent-stack-set-id": "An error occurred (StackSetNotFoundException) when calling the CreateStackInstances operation: StackSet not found" + } } } diff --git a/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json b/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json index f7406f8c55f29..648d349cdb9ad 100644 --- a/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json +++ b/tests/aws/services/cloudformation/resources/test_stack_sets.validation.json @@ -1,5 +1,14 @@ { "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": { "last_validated_date": "2023-05-24T13:32:47+00:00" + }, + "tests/aws/services/cloudformation/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": { + "last_validated_date": "2025-08-01T09:56:22+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 0.58, + "teardown": 0.0, + "total": 1.73 + } } } diff --git a/tests/aws/services/cloudformation/resources/test_stepfunctions.py b/tests/aws/services/cloudformation/resources/test_stepfunctions.py index 36b807157c367..befb779c2e42e 100644 --- a/tests/aws/services/cloudformation/resources/test_stepfunctions.py +++ b/tests/aws/services/cloudformation/resources/test_stepfunctions.py @@ -4,6 +4,7 @@ import pytest from localstack_snapshot.snapshots.transformer import JsonpathTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v2_provider from localstack import config from localstack.testing.pytest import markers @@ -46,6 +47,10 @@ def _is_executed(): assert "hello from statemachine" in execution_desc["output"] +@skip_if_v2_provider( + "Engine", + reason="During change set describe the a Ref to a not yet deployed resource returns null which is an invalid input for Fn::Split", +) @markers.aws.validated def test_nested_statemachine_with_sync2(deploy_cfn_template, aws_client): stack = deploy_cfn_template( diff --git a/tests/aws/services/cloudformation/v2/test_change_set_conditions.py b/tests/aws/services/cloudformation/test_change_set_conditions.py similarity index 89% rename from tests/aws/services/cloudformation/v2/test_change_set_conditions.py rename to tests/aws/services/cloudformation/test_change_set_conditions.py index f6b5661736f37..a567b99cd4ccd 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_conditions.py +++ b/tests/aws/services/cloudformation/test_change_set_conditions.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -28,12 +23,6 @@ ) class TestChangeSetConditions: @markers.aws.validated - @pytest.mark.skip( - reason=( - "The inclusion of response parameters in executor is in progress, " - "currently it cannot delete due to missing topic arn in the request" - ) - ) def test_condition_update_removes_resource( self, snapshot, @@ -110,10 +99,6 @@ def test_condition_update_adds_resource( capture_update_process(snapshot, template_1, template_2) @markers.aws.validated - @pytest.mark.skip( - reason="The inclusion of response parameters in executor is in progress, " - "currently it cannot delete due to missing topic arn in the request" - ) def test_condition_add_new_negative_condition_to_existent_resource( self, snapshot, diff --git a/tests/aws/services/cloudformation/v2/test_change_set_conditions.snapshot.json b/tests/aws/services/cloudformation/test_change_set_conditions.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_conditions.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_conditions.snapshot.json index 147c4f2eae447..b479512b031d5 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_conditions.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_conditions.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { "recorded-date": "15-04-2025, 13:51:50", "recorded-content": { "create-change-set-1": { @@ -407,7 +407,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { "recorded-date": "15-04-2025, 14:31:36", "recorded-content": { "create-change-set-1": { @@ -766,7 +766,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { "recorded-date": "15-04-2025, 15:11:48", "recorded-content": { "create-change-set-1": { @@ -1174,7 +1174,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { "recorded-date": "15-04-2025, 16:00:40", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_conditions.validation.json b/tests/aws/services/cloudformation/test_change_set_conditions.validation.json new file mode 100644 index 0000000000000..ebc592903e0bb --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_conditions.validation.json @@ -0,0 +1,14 @@ +{ + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { + "last_validated_date": "2025-04-15T15:11:48+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { + "last_validated_date": "2025-04-15T16:00:39+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { + "last_validated_date": "2025-04-15T14:31:36+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { + "last_validated_date": "2025-04-15T13:51:50+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.py b/tests/aws/services/cloudformation/test_change_set_depends_on.py similarity index 96% rename from tests/aws/services/cloudformation/v2/test_change_set_depends_on.py rename to tests/aws/services/cloudformation/test_change_set_depends_on.py index e4f7545a5667d..bba7112fb25aa 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.py +++ b/tests/aws/services/cloudformation/test_change_set_depends_on.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.snapshot.json b/tests/aws/services/cloudformation/test_change_set_depends_on.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_depends_on.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_depends_on.snapshot.json index 1c31c72649fa4..ab9c65a4e310d 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_depends_on.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { "recorded-date": "19-05-2025, 12:55:10", "recorded-content": { "create-change-set-1": { @@ -456,7 +456,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { "recorded-date": "19-05-2025, 13:01:35", "recorded-content": { "create-change-set-1": { @@ -913,7 +913,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { "recorded-date": "19-05-2025, 18:10:11", "recorded-content": { "create-change-set-1": { @@ -1349,7 +1349,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { "recorded-date": "19-05-2025, 18:13:11", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_depends_on.validation.json b/tests/aws/services/cloudformation/test_change_set_depends_on.validation.json new file mode 100644 index 0000000000000..847dc940ebea5 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_depends_on.validation.json @@ -0,0 +1,14 @@ +{ + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { + "last_validated_date": "2025-05-19T18:10:11+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { + "last_validated_date": "2025-05-19T18:13:11+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { + "last_validated_date": "2025-05-19T12:55:09+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { + "last_validated_date": "2025-05-19T13:01:34+00:00" + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_exports_imports.py b/tests/aws/services/cloudformation/test_change_set_exports_imports.py new file mode 100644 index 0000000000000..e91f1bf441db3 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_exports_imports.py @@ -0,0 +1,245 @@ +import json +import os + +import pytest +from botocore.exceptions import WaiterError +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider + +from localstack.aws.api.cloudformation import ChangeSetType +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@pytest.fixture +def execute_change_set(aws_client): + def inner(change_set_name: str, stack_name: str | None = None): + aws_client.cloudformation.execute_change_set( + ChangeSetName=change_set_name, StackName=stack_name + ) + aws_client.cloudformation.describe_stacks(StackName=stack_name) + aws_client.cloudformation.get_waiter("stack_create_complete").wait( + StackName=stack_name, + WaiterConfig={ + "Delay": 5, + "MaxAttempts": 100, + }, + ) + + return inner + + +@skip_if_v1_provider("Not supported on V1") +@markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Changes..ResourceChange.Details", + "$..Changes..ResourceChange.Scope", + "$..Parameters", + ] +) +class TestChangeSetImportExport: + @markers.aws.validated + def test_describe_change_set_import( + self, + snapshot, + aws_client, + deploy_cfn_template, + execute_change_set, + cleanup_stacks, + cleanups, + ): + export_name = f"b-{short_uid()}" + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/cfn_function_export.yml" + ), + parameters={"BucketExportName": export_name}, + ) + + empty_stack_name = f"stack-{short_uid()}" + change_set_body = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": {"Fn::ImportValue": export_name}}, + } + } + } + + change_set_name = f"change-set-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=empty_stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(change_set_body), + ChangeSetType=ChangeSetType.CREATE, + ) + cleanups.append(lambda: cleanup_stacks([empty_stack_name])) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, StackName=empty_stack_name, WaiterConfig={"Delay": 2} + ) + + describe = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=False + ) + + describe_with_values = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=True + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("Value"), + snapshot.transform.key_value("ChangeSetId"), + snapshot.transform.key_value("ChangeSetName"), + snapshot.transform.key_value("StackId"), + snapshot.transform.key_value("StackName"), + ] + ) + + snapshot.match("describe", describe) + snapshot.match("describe_with_values", describe_with_values) + + execute_change_set(change_set_name, empty_stack_name) + stack = aws_client.cloudformation.describe_stacks(StackName=empty_stack_name)["Stacks"][0] + snapshot.match("post-deploy-stack-describe", stack) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..ChangeSetId", + "$..DeletionTime", + # TODO: should be ROLLBACK_COMPLETE, instead is CREATE_FAILED + "$..StackStatus", + ] + ) + def test_describe_change_set_import_non_existent_export( + self, + snapshot, + aws_client, + deploy_cfn_template, + execute_change_set, + cleanup_stacks, + cleanups, + ): + export_name = f"b-{short_uid()}" + empty_stack_name = f"stack-{short_uid()}" + change_set_body = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": {"Fn::ImportValue": export_name}}, + } + } + } + + change_set_name = f"change-set-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=empty_stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(change_set_body), + ChangeSetType=ChangeSetType.CREATE, + ) + cleanups.append(lambda: cleanup_stacks([empty_stack_name])) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, StackName=empty_stack_name, WaiterConfig={"Delay": 2} + ) + + describe = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=False + ) + + describe_with_values = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=True + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(export_name, ""), + snapshot.transform.key_value("ChangeSetId"), + snapshot.transform.key_value("ChangeSetName"), + snapshot.transform.key_value("StackId"), + snapshot.transform.key_value("StackName"), + ] + ) + + snapshot.match("describe", describe) + snapshot.match("describe_with_values", describe_with_values) + + with pytest.raises(WaiterError): + execute_change_set(change_set_name, empty_stack_name) + stack = aws_client.cloudformation.describe_stacks(StackName=empty_stack_name)["Stacks"][0] + snapshot.match("post-deploy-stack-describe", stack) + + @markers.aws.validated + def test_describe_change_set_import_non_existent_export_then_create( + self, + snapshot, + aws_client, + deploy_cfn_template, + execute_change_set, + cleanup_stacks, + cleanups, + ): + """ + * First create a change set that refers to a non-existent export + * Then create the export + * Then execute the change set + """ + export_name = f"b-{short_uid()}" + empty_stack_name = f"stack-{short_uid()}" + change_set_body = { + "Resources": { + "MyFoo": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Type": "String", "Value": {"Fn::ImportValue": export_name}}, + } + } + } + + change_set_name = f"change-set-{short_uid()}" + aws_client.cloudformation.create_change_set( + StackName=empty_stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(change_set_body), + ChangeSetType=ChangeSetType.CREATE, + ) + cleanups.append(lambda: cleanup_stacks([empty_stack_name])) + + aws_client.cloudformation.get_waiter("change_set_create_complete").wait( + ChangeSetName=change_set_name, StackName=empty_stack_name, WaiterConfig={"Delay": 2} + ) + + describe = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=False + ) + + describe_with_values = aws_client.cloudformation.describe_change_set( + StackName=empty_stack_name, ChangeSetName=change_set_name, IncludePropertyValues=True + ) + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(export_name, ""), + snapshot.transform.key_value("ChangeSetId"), + snapshot.transform.key_value("ChangeSetName"), + snapshot.transform.key_value("StackId"), + snapshot.transform.key_value("StackName"), + ] + ) + + snapshot.match("describe", describe) + snapshot.match("describe_with_values", describe_with_values) + + # deploy the stack with the export + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/cfn_function_export.yml" + ), + parameters={"BucketExportName": export_name}, + ) + + # now execute the change set + execute_change_set(change_set_name, empty_stack_name) + stack = aws_client.cloudformation.describe_stacks(StackName=empty_stack_name)["Stacks"][0] + snapshot.match("post-deploy-stack-describe", stack) diff --git a/tests/aws/services/cloudformation/test_change_set_exports_imports.snapshot.json b/tests/aws/services/cloudformation/test_change_set_exports_imports.snapshot.json new file mode 100644 index 0000000000000..73635a2e85a39 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_exports_imports.snapshot.json @@ -0,0 +1,259 @@ +{ + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import": { + "recorded-date": "05-08-2025, 09:48:09", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_with_values": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-deploy-stack-describe": { + "ChangeSetId": "", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export": { + "recorded-date": "05-08-2025, 09:55:27", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_with_values": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "StatusReason": "[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [MyFoo] because: No export named ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-deploy-stack-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "ROLLBACK_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export_then_create": { + "recorded-date": "05-08-2025, 09:58:06", + "recorded-content": { + "describe": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe_with_values": { + "Capabilities": [], + "ChangeSetId": "", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyFoo", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "StatusReason": "[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [MyFoo] because: No export named ", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-deploy-stack-describe": { + "ChangeSetId": "", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + } + } + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_exports_imports.validation.json b/tests/aws/services/cloudformation/test_change_set_exports_imports.validation.json new file mode 100644 index 0000000000000..d3a8f3bb4a2ef --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_exports_imports.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import": { + "last_validated_date": "2025-08-05T09:48:09+00:00", + "durations_in_seconds": { + "setup": 1.32, + "call": 33.43, + "teardown": 8.03, + "total": 42.78 + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export": { + "last_validated_date": "2025-08-05T09:55:27+00:00", + "durations_in_seconds": { + "setup": 0.93, + "call": 10.52, + "teardown": 2.54, + "total": 13.99 + } + }, + "tests/aws/services/cloudformation/test_change_set_exports_imports.py::TestChangeSetImportExport::test_describe_change_set_import_non_existent_export_then_create": { + "last_validated_date": "2025-08-05T09:58:06+00:00", + "durations_in_seconds": { + "setup": 1.15, + "call": 33.86, + "teardown": 8.08, + "total": 43.09 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py b/tests/aws/services/cloudformation/test_change_set_fn_base64.py similarity index 90% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py rename to tests/aws/services/cloudformation/test_change_set_fn_base64.py index b8593dad92bd7..44e99a379fbf8 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_base64.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_base64.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_base64.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_base64.snapshot.json index bfec63bc4521b..46d93da24e484 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_base64.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { "recorded-date": "02-06-2025, 17:27:21", "recorded-content": { "create-change-set-1": { @@ -377,7 +377,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { "recorded-date": "02-06-2025, 17:28:46", "recorded-content": { "create-change-set-1": { @@ -755,7 +755,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { "recorded-date": "02-06-2025, 17:30:12", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_base64.validation.json similarity index 57% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_base64.validation.json rename to tests/aws/services/cloudformation/test_change_set_fn_base64.validation.json index b29b77f2c4405..818d0c8e783a9 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_base64.validation.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_base64.validation.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_add_to_static_property": { "last_validated_date": "2025-06-02T17:27:21+00:00", "durations_in_seconds": { "setup": 0.81, @@ -8,7 +8,7 @@ "total": 84.61 } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_change_input_string": { "last_validated_date": "2025-06-02T17:30:12+00:00", "durations_in_seconds": { "setup": 0.0, @@ -17,7 +17,7 @@ "total": 85.61 } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_base64.py::TestChangeSetFnBase64::test_fn_base64_remove_from_static_property": { "last_validated_date": "2025-06-02T17:28:46+00:00", "durations_in_seconds": { "setup": 0.0, diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.py similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py rename to tests/aws/services/cloudformation/test_change_set_fn_get_attr.py index 5255ff0704736..ec11138588659 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.py @@ -1,15 +1,12 @@ import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +14,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_get_attr.snapshot.json index c9a382f83c5d3..8cdfbcbee1fe8 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { "recorded-date": "08-04-2025, 11:24:14", "recorded-content": { "create-change-set-1": { @@ -511,7 +511,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { "recorded-date": "08-04-2025, 12:17:00", "recorded-content": { "create-change-set-1": { @@ -1137,7 +1137,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { "recorded-date": "08-04-2025, 12:20:19", "recorded-content": { "create-change-set-1": { @@ -1596,7 +1596,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { "recorded-date": "08-04-2025, 12:33:53", "recorded-content": { "create-change-set-1": { @@ -1963,7 +1963,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { "recorded-date": "08-04-2025, 12:36:41", "recorded-content": { "create-change-set-1": { @@ -2380,7 +2380,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { "recorded-date": "08-04-2025, 14:46:11", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_get_attr.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.validation.json new file mode 100644 index 0000000000000..2bd651509bd74 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_get_attr.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { + "last_validated_date": "2025-04-08T11:24:14+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { + "last_validated_date": "2025-04-08T14:46:11+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { + "last_validated_date": "2025-04-08T12:20:18+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { + "last_validated_date": "2025-04-08T12:17:00+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { + "last_validated_date": "2025-04-08T12:33:53+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { + "last_validated_date": "2025-04-08T12:36:40+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.py b/tests/aws/services/cloudformation/test_change_set_fn_join.py similarity index 96% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_join.py rename to tests/aws/services/cloudformation/test_change_set_fn_join.py index 718f1a1181043..90d784c9940e6 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_join.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_join.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_join.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_join.snapshot.json index ab448456fa342..e05a29dd37464 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_join.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { "recorded-date": "05-05-2025, 13:10:55", "recorded-content": { "create-change-set-1": { @@ -386,7 +386,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { "recorded-date": "05-05-2025, 13:15:58", "recorded-content": { "create-change-set-1": { @@ -773,7 +773,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { "recorded-date": "05-05-2025, 13:24:03", "recorded-content": { "create-change-set-1": { @@ -1285,7 +1285,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { "recorded-date": "05-05-2025, 13:31:26", "recorded-content": { "create-change-set-1": { @@ -1797,7 +1797,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { "recorded-date": "05-05-2025, 13:37:54", "recorded-content": { "create-change-set-1": { @@ -2184,7 +2184,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { "recorded-date": "05-05-2025, 13:42:26", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_join.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_join.validation.json new file mode 100644 index 0000000000000..728504d82bfb2 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_join.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { + "last_validated_date": "2025-05-05T13:31:26+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { + "last_validated_date": "2025-05-05T13:24:03+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { + "last_validated_date": "2025-05-05T13:10:54+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { + "last_validated_date": "2025-05-05T13:42:26+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { + "last_validated_date": "2025-05-05T13:15:57+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { + "last_validated_date": "2025-05-05T13:37:54+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.py b/tests/aws/services/cloudformation/test_change_set_fn_select.py similarity index 95% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_select.py rename to tests/aws/services/cloudformation/test_change_set_fn_select.py index 16b5dee524632..c2597ba35eb1b 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_select.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_select.snapshot.json similarity index 98% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_select.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_select.snapshot.json index 3e286c96554e9..3973e17b16508 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_select.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { "recorded-date": "28-05-2025, 13:14:01", "recorded-content": { "create-change-set-1": { @@ -377,7 +377,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { "recorded-date": "28-05-2025, 13:17:47", "recorded-content": { "create-change-set-1": { @@ -755,7 +755,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { "recorded-date": "28-05-2025, 13:21:34", "recorded-content": { "create-change-set-1": { @@ -1133,7 +1133,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { "recorded-date": "28-05-2025, 13:23:46", "recorded-content": { "create-change-set-1": { @@ -1511,7 +1511,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { "recorded-date": "28-05-2025, 13:32:24", "recorded-content": { "create-change-set-1": { @@ -1889,11 +1889,11 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_index_select_from_parameter_list": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_index_select_from_parameter_list": { "recorded-date": "28-05-2025, 13:56:52", "recorded-content": {} }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { "recorded-date": "28-05-2025, 14:44:47", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_select.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_select.validation.json new file mode 100644 index 0000000000000..b57493ef0f50d --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_select.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { + "last_validated_date": "2025-05-28T13:14:01+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { + "last_validated_date": "2025-05-28T14:44:47+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { + "last_validated_date": "2025-05-28T13:32:24+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { + "last_validated_date": "2025-05-28T13:23:46+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { + "last_validated_date": "2025-05-28T13:21:34+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { + "last_validated_date": "2025-05-28T13:17:47+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.py b/tests/aws/services/cloudformation/test_change_set_fn_split.py similarity index 96% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_split.py rename to tests/aws/services/cloudformation/test_change_set_fn_split.py index fd85f7a61011c..1743c4cc9da00 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_split.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_split.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_split.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_split.snapshot.json index b31381319abae..fa687acb00f0c 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_split.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { "recorded-date": "02-06-2025, 11:19:05", "recorded-content": { "create-change-set-1": { @@ -377,7 +377,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { "recorded-date": "02-06-2025, 11:20:30", "recorded-content": { "create-change-set-1": { @@ -755,7 +755,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { "recorded-date": "02-06-2025, 11:22:03", "recorded-content": { "create-change-set-1": { @@ -1133,7 +1133,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { "recorded-date": "02-06-2025, 11:23:28", "recorded-content": { "create-change-set-1": { @@ -1577,7 +1577,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { "recorded-date": "02-06-2025, 11:26:00", "recorded-content": { "create-change-set-1": { @@ -2074,7 +2074,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { "recorded-date": "02-06-2025, 12:30:32", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_split.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_split.validation.json new file mode 100644 index 0000000000000..3ecf3eeb393eb --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_split.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { + "last_validated_date": "2025-06-02T11:19:05+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { + "last_validated_date": "2025-06-02T12:30:32+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { + "last_validated_date": "2025-06-02T11:22:03+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { + "last_validated_date": "2025-06-02T11:20:29+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { + "last_validated_date": "2025-06-02T11:26:00+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { + "last_validated_date": "2025-06-02T11:23:28+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py b/tests/aws/services/cloudformation/test_change_set_fn_sub.py similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py rename to tests/aws/services/cloudformation/test_change_set_fn_sub.py index 82984d02da21e..6646589f1f7e2 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py +++ b/tests/aws/services/cloudformation/test_change_set_fn_sub.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_sub.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_fn_sub.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_fn_sub.snapshot.json index d11042ed00882..6ccd91b746d77 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_fn_sub.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { "recorded-date": "20-05-2025, 09:54:49", "recorded-content": { "create-change-set-1": { @@ -385,7 +385,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { "recorded-date": "20-05-2025, 09:59:44", "recorded-content": { "create-change-set-1": { @@ -771,7 +771,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { "recorded-date": "20-05-2025, 11:29:16", "recorded-content": { "create-change-set-1": { @@ -1157,7 +1157,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { "recorded-date": "20-05-2025, 11:54:12", "recorded-content": { "create-change-set-1": { @@ -1543,7 +1543,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { "recorded-date": "20-05-2025, 12:01:36", "recorded-content": { "create-change-set-1": { @@ -1929,7 +1929,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { "recorded-date": "20-05-2025, 12:05:00", "recorded-content": { "create-change-set-1": { @@ -2315,7 +2315,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { "recorded-date": "20-05-2025, 15:08:40", "recorded-content": { "create-change-set-1": { @@ -2749,7 +2749,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { "recorded-date": "20-05-2025, 15:10:16", "recorded-content": { "create-change-set-1": { @@ -3183,7 +3183,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { "recorded-date": "20-05-2025, 15:26:13", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_fn_sub.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_sub.validation.json new file mode 100644 index 0000000000000..a8c8278cdb8a9 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_sub.validation.json @@ -0,0 +1,29 @@ +{ + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { + "last_validated_date": "2025-05-20T15:26:12+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { + "last_validated_date": "2025-05-20T11:54:12+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { + "last_validated_date": "2025-05-20T15:08:40+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { + "last_validated_date": "2025-05-20T09:54:49+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { + "last_validated_date": "2025-05-20T12:05:00+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { + "last_validated_date": "2025-05-20T11:29:16+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { + "last_validated_date": "2025-05-20T12:01:36+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { + "last_validated_date": "2025-05-20T15:10:15+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { + "last_validated_date": "2025-05-20T09:59:44+00:00" + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_fn_transform.py b/tests/aws/services/cloudformation/test_change_set_fn_transform.py new file mode 100644 index 0000000000000..60a0387b436b5 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_transform.py @@ -0,0 +1,572 @@ +import os + +import pytest +from localstack_snapshot.snapshots.transformer import RegexTransformer + +from localstack.aws.api.lambda_ import Runtime +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + + +@pytest.mark.skip(reason="Not implemented with either provider") +@markers.snapshot.skip_snapshot_verify( + paths=[ + "per-resource-events..*", + "delete-describe..*", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..Replacement", + "$..PolicyAction", + ] +) +class TestChangeSetFnTransform: + @pytest.fixture(scope="function") + def create_macro(self, aws_client, deploy_cfn_template, create_lambda_function): + def _inner(macro_name, code_path): + func_name = f"test_lambda_{short_uid()}" + create_lambda_function( + func_name=func_name, + handler_file=code_path, + runtime=Runtime.python3_12, + client=aws_client.lambda_, + ) + + deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../../templates/macro_resource.yml" + ), + parameters={"FunctionName": func_name, "MacroName": macro_name}, + ) + + yield _inner + + @markers.aws.validated + @pytest.mark.parametrize("include_format", ["yml", "json"]) + @markers.snapshot.skip_snapshot_verify( + paths=["$..Changes..ResourceChange.AfterContext.Properties.Name"] + ) + def test_embedded_fn_transform_include( + self, include_format, snapshot, capture_update_process, s3_bucket, aws_client, tmp_path + ): + name1 = f"name-1-{short_uid()}" + name2 = f"name-2-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + bucket = s3_bucket + file = tmp_path / "bucket_definition.yml" + + if include_format == "json": + template = ( + '{"Parameter": { "Type": "AWS::SSM::Parameter","Properties": {"Name": "%s", "Type": "String", "Value": "foo"}}}' + % name2 + ) + else: + template = f""" + Parameter2: + Type: AWS::SSM::Parameter + Properties: + Name: {name2} + Type: String + Value: foo + """ + + file.write_text(data=template) + aws_client.s3.upload_file( + Bucket=bucket, + Key="template", + Filename=str(file.absolute()), + ) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + } + } + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + "Fn::Transform": { + "Name": "AWS::Include", + "Parameters": {"Location": f"s3://{bucket}/template"}, + }, + }, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @pytest.mark.parametrize("include_format", ["yml", "json"]) + def test_global_fn_transform_include( + self, include_format, snapshot, capture_update_process, s3_bucket, aws_client, tmp_path + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + bucket = s3_bucket + file = tmp_path / "bucket_definition.yml" + + if include_format == "json": + template = '{"Outputs":{"ParameterRef":{"Value":{"Ref":"Parameter"}}}} ' + else: + template = """ + Outputs: + ParameterRef: + Value: + Ref: Parameter + """ + + file.write_text(data=template) + aws_client.s3.upload_file( + Bucket=bucket, + Key="template", + Filename=str(file.absolute()), + ) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + } + } + template_2 = { + "Transform": { + "Name": "AWS::Include", + "Parameters": {"Location": f"s3://{bucket}/template"}, + }, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + }, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + "$..Changes..ResourceChange.AfterContext.Properties.Body.paths", + "$..Changes..ResourceChange.AfterContext.Properties.SourceArn", + ] + ) + def test_serverless_fn_transform( + self, snapshot, capture_update_process, s3_bucket, aws_client, tmp_path + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "topic-name-1")) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Value": "{Substitution}", "Type": "String", "Name": name1}, + } + } + } + template_2 = { + "Transform": "AWS::Serverless-2016-10-31", + "Resources": { + "HelloWorldFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "index.handler", + "Runtime": "nodejs18.x", + "InlineCode": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};", + "Events": { + "ApiEvent": { + "Type": "Api", + "Properties": {"Path": "/hello", "Method": "get"}, + } + }, + }, + } + }, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_global_macro_fn_transform( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "name-1")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/replace_string.py" + ) + macro_name = "Substitution" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Value": "original", "Type": "String", "Name": name1}, + } + } + } + + template_2 = { + "Parameters": {"Substitution": {"Type": "String", "Default": "SubstitutionDefault"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Value": "{Substitution}", "Type": "String", "Name": name1}, + } + }, + "Transform": {"Name": macro_name}, + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_embedded_macro_fn_transform( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"name-1-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "name-1")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/add_standard_tags.py" + ) + macro_name = "AddTags" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + }, + } + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": "foo", + "Fn::Transform": macro_name, + }, + } + } + } + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_embedded_macro_for_attribute_fn_transform( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/return_random_string.py" + ) + macro_name = "GenerateRandom" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + "Parameters": {"Prefix": {"Ref": "Input"}}, + } + }, + }, + } + }, + } + + capture_update_process(snapshot, template_1, template_2, p2={"Input": "test"}) + + @markers.aws.validated + def test_multiple_fn_transform_order( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/replace_string.py" + ) + macro_name = "ReplaceString" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Value": "", + "Type": "String", + "Fn::Transform": [ + {"Name": "ReplaceString", "Parameters": {"Input": "snippet-transform"}}, + { + "Name": "ReplaceString", + "Parameters": {"Input": "second-snippet-transform"}, + }, + ], + }, + } + }, + "Transform": [ + {"Name": "ReplaceString", "Parameters": {"Input": "global-transform"}}, + {"Name": "ReplaceString", "Parameters": {"Input": "second-global-transform"}}, + ], + } + + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + @pytest.mark.parametrize("transform", ["true", "false"]) + def test_conditional_transform( + self, + transform, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/replace_string.py" + ) + macro_name = "ReplaceString" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Parameters": {"Transform": {"Type": "String"}}, + "Conditions": {"Deploy": {"Fn::Equals": [{"Ref": "Transform"}, "true"]}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Condition": "Deploy", + "Properties": { + "Name": name1, + "Value": "", + "Type": "String", + "Fn::Transform": [ + {"Name": "ReplaceString", "Parameters": {"Input": "snippet-transform"}}, + ], + }, + } + }, + } + + capture_update_process(snapshot, template_1, template_2, p2={"Transform": transform}) + + @markers.aws.validated + def test_macro_with_intrinsic_function( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/replace_string.py" + ) + macro_name = "ReplaceString" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Value": "", + "Type": "String", + "Fn::Transform": [ + { + "Name": macro_name, + "Parameters": {"Input": {"Fn::Join": ["-", ["test", "string"]]}}, + }, + ], + }, + } + } + } + + capture_update_process(snapshot, template_1, template_2) + + @markers.aws.validated + def test_remove_transform_in_update_change_set( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/return_random_string.py" + ) + macro_name = "GenerateRandom" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + } + }, + }, + } + }, + } + + template_2 = { + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": {"Name": name1, "Type": "String", "Value": "foo"}, + } + } + } + + capture_update_process(snapshot, template_1, template_2, p1={"Input": "test"}) + + @markers.aws.validated + def test_update_parameter_transform_in_update_change_set( + self, + snapshot, + capture_update_process, + create_macro, + ): + name1 = f"parameter-{short_uid()}" + snapshot.add_transformer(RegexTransformer(name1, "parameter-name")) + snapshot.add_transformer(snapshot.transform.key_value("Value", "value")) + + macro_function_path = os.path.join( + os.path.dirname(__file__), "../../../templates/macros/return_random_string.py" + ) + macro_name = "GenerateRandom" + create_macro(macro_name, macro_function_path) + + template_1 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + } + }, + }, + } + }, + } + + template_2 = { + "Parameters": {"Input": {"Type": "String"}}, + "Resources": { + "Parameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": name1, + "Type": "String", + "Value": { + "Fn::Transform": { + "Name": "GenerateRandom", + } + }, + }, + } + }, + } + + capture_update_process( + snapshot, template_1, template_2, p1={"Input": "test"}, p2={"Input": "test2"} + ) diff --git a/tests/aws/services/cloudformation/test_change_set_fn_transform.snapshot.json b/tests/aws/services/cloudformation/test_change_set_fn_transform.snapshot.json new file mode 100644 index 0000000000000..791db32c02450 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_transform.snapshot.json @@ -0,0 +1,6505 @@ +{ + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[yml]": { + "recorded-date": "03-06-2025, 21:29:28", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-2-bd1e2d36" + } + }, + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "Parameter2": [ + { + "EventId": "Parameter2-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "name-2-bd1e2d36", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-bd1e2d36" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter2-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "name-2-bd1e2d36", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-bd1e2d36" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter2-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-bd1e2d36" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[json]": { + "recorded-date": "03-06-2025, 21:29:58", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-2-3e00e97a" + } + }, + "BeforeContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "name-2-3e00e97a", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "topic-name-1", + "Name": "Name", + "Path": "/Properties/Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-5cbf35a2-4a92-4554-a111-1f96269d3814", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-06c1c753-8966-4dc1-9a39-cfbe492abedb", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-2-3e00e97a", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-3e00e97a" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-2-3e00e97a", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-3e00e97a" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-2-3e00e97a" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "Requested update requires the creation of a new physical resource; hence creating one.", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[json]": { + "recorded-date": "03-06-2025, 21:30:48", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[yml]": { + "recorded-date": "03-06-2025, 21:30:23", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "ParameterRef", + "OutputValue": "topic-name-1" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_serverless_fn_transform": { + "recorded-date": "03-06-2025, 21:32:10", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{Substitution}", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Role": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Handler": "index.handler", + "Runtime": "nodejs18.x", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "Details": [], + "LogicalResourceId": "HelloWorldFunction", + "ResourceType": "AWS::Lambda::Function", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "FunctionName": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Action": "lambda:InvokeFunction", + "SourceArn": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Principal": "apigateway.amazonaws.com" + } + }, + "Details": [], + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "ResourceType": "AWS::Lambda::Permission", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "Details": [], + "LogicalResourceId": "HelloWorldFunctionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "{Substitution}", + "Type": "String", + "Name": "topic-name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/{{changeSet:KNOWN_AFTER_APPLY}}/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + } + }, + "Details": [], + "LogicalResourceId": "ServerlessRestApi", + "ResourceType": "AWS::ApiGateway::RestApi", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "RestApiId": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage" + } + }, + "Details": [], + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "ResourceType": "AWS::ApiGateway::Deployment", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "RestApiId": "{{changeSet:KNOWN_AFTER_APPLY}}", + "DeploymentId": "{{changeSet:KNOWN_AFTER_APPLY}}", + "StageName": "Prod" + } + }, + "Details": [], + "LogicalResourceId": "ServerlessRestApiProdStage", + "ResourceType": "AWS::ApiGateway::Stage", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "HelloWorldFunction", + "ResourceType": "AWS::Lambda::Function", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "ResourceType": "AWS::Lambda::Permission", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "HelloWorldFunctionRole", + "ResourceType": "AWS::IAM::Role", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ServerlessRestApi", + "ResourceType": "AWS::ApiGateway::RestApi", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "ResourceType": "AWS::ApiGateway::Deployment", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "ServerlessRestApiProdStage", + "ResourceType": "AWS::ApiGateway::Stage", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "HelloWorldFunction": [ + { + "EventId": "HelloWorldFunction-CREATE_COMPLETE-date", + "LogicalResourceId": "HelloWorldFunction", + "PhysicalResourceId": "-HelloWorldFunction-EdXB90Au1l86", + "ResourceProperties": { + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs18.x", + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::Lambda::Function", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunction-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunction", + "PhysicalResourceId": "-HelloWorldFunction-EdXB90Au1l86", + "ResourceProperties": { + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs18.x", + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::Lambda::Function", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunction-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunction", + "PhysicalResourceId": "", + "ResourceProperties": { + "Role": "arn::iam::111111111111:role/", + "Runtime": "nodejs18.x", + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n return {\n statusCode: 200,\n body: JSON.stringify({ message: 'Hello from SAM inline function!' })\n };\n};" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::Lambda::Function", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "HelloWorldFunctionApiEventPermissionProd": [ + { + "EventId": "HelloWorldFunctionApiEventPermissionProd-CREATE_COMPLETE-date", + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "PhysicalResourceId": "-HelloWorldFunctionApiEventPermissionProd-xfMFMMYChbqn", + "ResourceProperties": { + "FunctionName": "-HelloWorldFunction-EdXB90Au1l86", + "Action": "lambda:InvokeFunction", + "SourceArn": "arn::execute-api::111111111111:317w2e8pc3/*/GET/", + "Principal": "apigateway.amazonaws.com" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::Lambda::Permission", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionApiEventPermissionProd-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "PhysicalResourceId": "-HelloWorldFunctionApiEventPermissionProd-xfMFMMYChbqn", + "ResourceProperties": { + "FunctionName": "-HelloWorldFunction-EdXB90Au1l86", + "Action": "lambda:InvokeFunction", + "SourceArn": "arn::execute-api::111111111111:317w2e8pc3/*/GET/", + "Principal": "apigateway.amazonaws.com" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::Lambda::Permission", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionApiEventPermissionProd-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionApiEventPermissionProd", + "PhysicalResourceId": "", + "ResourceProperties": { + "FunctionName": "-HelloWorldFunction-EdXB90Au1l86", + "Action": "lambda:InvokeFunction", + "SourceArn": "arn::execute-api::111111111111:317w2e8pc3/*/GET/", + "Principal": "apigateway.amazonaws.com" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::Lambda::Permission", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "HelloWorldFunctionRole": [ + { + "EventId": "HelloWorldFunctionRole-CREATE_COMPLETE-date", + "LogicalResourceId": "HelloWorldFunctionRole", + "PhysicalResourceId": "", + "ResourceProperties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::IAM::Role", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionRole-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionRole", + "PhysicalResourceId": "", + "ResourceProperties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::IAM::Role", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "HelloWorldFunctionRole-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "HelloWorldFunctionRole", + "PhysicalResourceId": "", + "ResourceProperties": { + "ManagedPolicyArns": [ + "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::IAM::Role", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "Parameter": [ + { + "EventId": "Parameter-af425eba-d8e2-4ad9-9ebc-153095f8161d", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-adcb9c2b-0276-4487-9df6-5f12bf586323", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "topic-name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "topic-name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ServerlessRestApi": [ + { + "EventId": "ServerlessRestApi-CREATE_COMPLETE-date", + "LogicalResourceId": "ServerlessRestApi", + "PhysicalResourceId": "317w2e8pc3", + "ResourceProperties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/arn::lambda::111111111111:function:-HelloWorldFunction-EdXB90Au1l86/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::ApiGateway::RestApi", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApi-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApi", + "PhysicalResourceId": "317w2e8pc3", + "ResourceProperties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/arn::lambda::111111111111:function:-HelloWorldFunction-EdXB90Au1l86/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::ApiGateway::RestApi", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApi-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApi", + "PhysicalResourceId": "", + "ResourceProperties": { + "Body": { + "paths": { + "/": { + "get": { + "responses": {}, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": "arn::apigateway::lambda:path/2015-03-31/functions/arn::lambda::111111111111:function:-HelloWorldFunction-EdXB90Au1l86/invocations" + } + } + } + }, + "swagger": "2.0", + "info": { + "title": "", + "version": "1.0" + } + } + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::ApiGateway::RestApi", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ServerlessRestApiDeployment47fc2d5f9d": [ + { + "EventId": "ServerlessRestApiDeployment47fc2d5f9d-CREATE_COMPLETE-date", + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "PhysicalResourceId": "5oj1e3", + "ResourceProperties": { + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::ApiGateway::Deployment", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiDeployment47fc2d5f9d-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "PhysicalResourceId": "5oj1e3", + "ResourceProperties": { + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::ApiGateway::Deployment", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiDeployment47fc2d5f9d-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiDeployment47fc2d5f9d", + "PhysicalResourceId": "", + "ResourceProperties": { + "Description": "RestApi deployment id: 47fc2d5f9d21ad56f83937abe2779d0e26d7095e", + "StageName": "Stage", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::ApiGateway::Deployment", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "ServerlessRestApiProdStage": [ + { + "EventId": "ServerlessRestApiProdStage-CREATE_COMPLETE-date", + "LogicalResourceId": "ServerlessRestApiProdStage", + "PhysicalResourceId": "Prod", + "ResourceProperties": { + "DeploymentId": "5oj1e3", + "StageName": "Prod", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::ApiGateway::Stage", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiProdStage-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiProdStage", + "PhysicalResourceId": "Prod", + "ResourceProperties": { + "DeploymentId": "5oj1e3", + "StageName": "Prod", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::ApiGateway::Stage", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "ServerlessRestApiProdStage-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "ServerlessRestApiProdStage", + "PhysicalResourceId": "", + "ResourceProperties": { + "DeploymentId": "5oj1e3", + "StageName": "Prod", + "RestApiId": "317w2e8pc3" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::ApiGateway::Stage", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_macro_fn_transform": { + "recorded-date": "03-06-2025, 21:42:32", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "original", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "{Substitution}", + "Type": "String", + "Name": "name-1" + } + }, + "BeforeContext": { + "Properties": { + "Value": "original", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "{Substitution}", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "original", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "{Substitution}", + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "original", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "original", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "original", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Substitution", + "ParameterValue": "SubstitutionDefault" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_fn_transform": { + "recorded-date": "03-06-2025, 21:51:11", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Tags": { + "MacroAdded": "True" + }, + "Name": "name-1" + } + }, + "BeforeContext": { + "Properties": { + "Value": "foo", + "Type": "String", + "Name": "name-1" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": { + "MacroAdded": "True" + }, + "Attribute": "Properties", + "AttributeChangeType": "Add", + "Name": "Tags", + "Path": "/Properties/Tags", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Tags", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Tags": { + "MacroAdded": "True" + }, + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Tags": { + "MacroAdded": "True" + }, + "Name": "name-1" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "name-1", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "foo", + "Name": "name-1" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_for_attribute_fn_transform": { + "recorded-date": "03-06-2025, 21:34:07", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_multiple_fn_transform_order": { + "recorded-date": "03-06-2025, 21:34:52", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "EventId": "Parameter-UPDATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-UPDATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_COMPLETE-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "Parameter-CREATE_IN_PROGRESS-date", + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceProperties": { + "Type": "String", + "Value": "", + "Name": "parameter-name" + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ], + "": [ + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + }, + { + "EventId": "", + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceStatusReason": "User Initiated", + "ResourceType": "AWS::CloudFormation::Stack", + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Timestamp": "timestamp" + } + ] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform": { + "recorded-date": "20-06-2025, 21:12:31", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[true]": { + "recorded-date": "20-06-2025, 21:15:45", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "true" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[false]": { + "recorded-date": "20-06-2025, 21:16:29", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Remove", + "Details": [], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "PolicyAction": "Delete", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Transform", + "ParameterValue": "false" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_macro_with_intrinsic_function": { + "recorded-date": "20-06-2025, 22:19:25", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_remove_transform_in_update_change_set": { + "recorded-date": "31-07-2025, 21:36:49", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_update_parameter_transform_in_update_change_set": { + "recorded-date": "31-07-2025, 21:38:55", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String", + "Name": "parameter-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "Input", + "ParameterValue": "test2" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "parameter-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + } +} diff --git a/tests/aws/services/cloudformation/test_change_set_fn_transform.validation.json b/tests/aws/services/cloudformation/test_change_set_fn_transform.validation.json new file mode 100644 index 0000000000000..16122b91de118 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_fn_transform.validation.json @@ -0,0 +1,137 @@ +{ + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform": { + "last_validated_date": "2025-06-20T21:12:32+00:00", + "durations_in_seconds": { + "setup": 10.79, + "call": 35.71, + "teardown": 5.56, + "total": 52.06 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[false]": { + "last_validated_date": "2025-06-20T21:16:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 38.41, + "teardown": 5.71, + "total": 44.12 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_conditional_transform[true]": { + "last_validated_date": "2025-06-20T21:15:45+00:00", + "durations_in_seconds": { + "setup": 10.81, + "call": 38.04, + "teardown": 5.12, + "total": 53.97 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[json]": { + "last_validated_date": "2025-06-03T21:29:58+00:00", + "durations_in_seconds": { + "setup": 0.39, + "call": 29.05, + "teardown": 0.82, + "total": 30.26 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_fn_transform_include[yml]": { + "last_validated_date": "2025-06-03T21:29:28+00:00", + "durations_in_seconds": { + "setup": 0.62, + "call": 26.34, + "teardown": 0.8, + "total": 27.76 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_fn_transform": { + "last_validated_date": "2025-06-03T21:51:12+00:00", + "durations_in_seconds": { + "setup": 10.68, + "call": 35.35, + "teardown": 5.52, + "total": 51.55 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_embedded_macro_for_attribute_fn_transform": { + "last_validated_date": "2025-06-03T21:34:07+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 36.99, + "teardown": 5.18, + "total": 42.17 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[json]": { + "last_validated_date": "2025-06-03T21:30:48+00:00", + "durations_in_seconds": { + "setup": 0.39, + "call": 23.99, + "teardown": 0.8, + "total": 25.18 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_fn_transform_include[yml]": { + "last_validated_date": "2025-06-03T21:30:23+00:00", + "durations_in_seconds": { + "setup": 0.37, + "call": 24.02, + "teardown": 0.89, + "total": 25.28 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_global_macro_fn_transform": { + "last_validated_date": "2025-06-03T21:42:32+00:00", + "durations_in_seconds": { + "setup": 10.81, + "call": 37.62, + "teardown": 5.34, + "total": 53.77 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_macro_with_function": { + "last_validated_date": "2025-06-20T22:19:25+00:00", + "durations_in_seconds": { + "setup": 10.9, + "call": 36.99, + "teardown": 5.46, + "total": 53.35 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_multiple_fn_transform_order": { + "last_validated_date": "2025-06-03T21:34:52+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 39.28, + "teardown": 5.61, + "total": 44.89 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_remove_transform_in_update_change_set": { + "last_validated_date": "2025-07-31T21:36:50+00:00", + "durations_in_seconds": { + "setup": 10.72, + "call": 36.7, + "teardown": 5.53, + "total": 52.95 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_serverless_fn_transform": { + "last_validated_date": "2025-06-03T21:32:10+00:00", + "durations_in_seconds": { + "setup": 0.35, + "call": 80.57, + "teardown": 0.71, + "total": 81.63 + } + }, + "tests/aws/services/cloudformation/v2/test_change_set_fn_transform.py::TestChangeSetFnTransform::test_update_parameter_transform_in_update_change_set": { + "last_validated_date": "2025-07-31T21:38:55+00:00", + "durations_in_seconds": { + "setup": 10.84, + "call": 36.02, + "teardown": 5.42, + "total": 52.28 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.py b/tests/aws/services/cloudformation/test_change_set_global_macros.py similarity index 91% rename from tests/aws/services/cloudformation/v2/test_change_set_global_macros.py rename to tests/aws/services/cloudformation/test_change_set_global_macros.py index ef31281f2096e..dd0085aadfa88 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.py +++ b/tests/aws/services/cloudformation/test_change_set_global_macros.py @@ -1,21 +1,17 @@ import json import os -import pytest from localstack_snapshot.snapshots.transformer import JsonpathTransformer, RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.testing.pytest.cloudformation.fixtures import _normalise_describe_change_set_output from localstack.utils.functions import call_safe from localstack.utils.strings import short_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -23,7 +19,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", @@ -51,7 +46,7 @@ def test_base_global_macro( ) ) macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../templates/macros/format_template.py" + os.path.dirname(__file__), "../../templates/macros/format_template.py" ) macro_name = "SubstitutionMacro" func_name = f"test_lambda_{short_uid()}" @@ -64,7 +59,7 @@ def test_base_global_macro( ) deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/macro_resource.yml" + os.path.dirname(__file__), "../../templates/macro_resource.yml" ), parameters={"FunctionName": func_name, "MacroName": macro_name}, ) @@ -119,7 +114,7 @@ def test_update_after_macro_for_before_version_is_deleted( ) snapshot.add_transformer(snapshot.transform.cloudformation_api()) macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../templates/macros/format_template.py" + os.path.dirname(__file__), "../../templates/macros/format_template.py" ) # Create the macro to be used in the first version of the template. @@ -135,7 +130,7 @@ def test_update_after_macro_for_before_version_is_deleted( ) macro_stack_first = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/macro_resource.yml" + os.path.dirname(__file__), "../../templates/macro_resource.yml" ), parameters={"FunctionName": func_name, "MacroName": macro_name_first}, ) @@ -160,7 +155,7 @@ def test_update_after_macro_for_before_version_is_deleted( ChangeSetName=change_set_name, TemplateBody=json.dumps(template_1), ChangeSetType="CREATE", - Parameters=list(), + Parameters=[], ) stack_id = change_set_details["StackId"] change_set_id = change_set_details["Id"] @@ -170,7 +165,7 @@ def test_update_after_macro_for_before_version_is_deleted( cleanups.append( lambda: call_safe( aws_client_no_retry.cloudformation.delete_change_set, - kwargs=dict(ChangeSetName=change_set_id), + kwargs={"ChangeSetName": change_set_id}, ) ) # Describe @@ -192,7 +187,7 @@ def test_update_after_macro_for_before_version_is_deleted( # ensure stack deletion cleanups.append( lambda: call_safe( - aws_client_no_retry.cloudformation.delete_stack, kwargs=dict(StackName=stack_id) + aws_client_no_retry.cloudformation.delete_stack, kwargs={"StackName": stack_id} ) ) describe = aws_client_no_retry.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][ @@ -216,7 +211,7 @@ def test_update_after_macro_for_before_version_is_deleted( ) macro_stack_second = deploy_cfn_template( template_path=os.path.join( - os.path.dirname(__file__), "../../../templates/macro_resource.yml" + os.path.dirname(__file__), "../../templates/macro_resource.yml" ), parameters={"FunctionName": func_name, "MacroName": macro_name_second}, ) @@ -244,7 +239,7 @@ def test_update_after_macro_for_before_version_is_deleted( ChangeSetName=change_set_name, TemplateBody=json.dumps(template_2), ChangeSetType="UPDATE", - Parameters=list(), + Parameters=[], ) stack_id = change_set_details["StackId"] change_set_id = change_set_details["Id"] diff --git a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.snapshot.json b/tests/aws/services/cloudformation/test_change_set_global_macros.snapshot.json similarity index 98% rename from tests/aws/services/cloudformation/v2/test_change_set_global_macros.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_global_macros.snapshot.json index 686fa970b25f9..7b5522ddbf3bc 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_global_macros.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { "recorded-date": "24-06-2025, 15:16:22", "recorded-content": { "create-change-set-1": { @@ -338,7 +338,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { "recorded-date": "26-06-2025, 11:59:06", "recorded-content": { "describe-change-set-1-prop-values": { diff --git a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.validation.json b/tests/aws/services/cloudformation/test_change_set_global_macros.validation.json similarity index 56% rename from tests/aws/services/cloudformation/v2/test_change_set_global_macros.validation.json rename to tests/aws/services/cloudformation/test_change_set_global_macros.validation.json index d88f126525af3..df980f6434101 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_global_macros.validation.json +++ b/tests/aws/services/cloudformation/test_change_set_global_macros.validation.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_base_global_macro": { "last_validated_date": "2025-06-24T15:16:22+00:00", "durations_in_seconds": { "setup": 11.62, @@ -8,7 +8,7 @@ "total": 52.03 } }, - "tests/aws/services/cloudformation/v2/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { + "tests/aws/services/cloudformation/test_change_set_global_macros.py::TestChangeSetGlobalMacros::test_update_after_macro_for_before_version_is_deleted": { "last_validated_date": "2025-06-26T11:59:07+00:00", "durations_in_seconds": { "setup": 11.46, diff --git a/tests/aws/services/cloudformation/v2/test_change_set_mappings.py b/tests/aws/services/cloudformation/test_change_set_mappings.py similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_mappings.py rename to tests/aws/services/cloudformation/test_change_set_mappings.py index 05fa11a2cce80..266246a3f9e28 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_mappings.py +++ b/tests/aws/services/cloudformation/test_change_set_mappings.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_mappings.snapshot.json b/tests/aws/services/cloudformation/test_change_set_mappings.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_mappings.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_mappings.snapshot.json index 58882da07da49..a194134d1dd56 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_mappings.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_mappings.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { "recorded-date": "15-04-2025, 13:03:18", "recorded-content": { "create-change-set-1": { @@ -386,7 +386,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { "recorded-date": "15-04-2025, 13:04:44", "recorded-content": { "create-change-set-1": { @@ -773,7 +773,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { "recorded-date": "15-04-2025, 13:05:52", "recorded-content": { "create-change-set-1": { @@ -1140,7 +1140,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { "recorded-date": "15-04-2025, 13:07:01", "recorded-content": { "create-change-set-1": { @@ -1507,7 +1507,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { "recorded-date": "15-04-2025, 13:08:27", "recorded-content": { "create-change-set-1": { @@ -1966,7 +1966,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { "recorded-date": "15-04-2025, 13:15:54", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_mappings.validation.json b/tests/aws/services/cloudformation/test_change_set_mappings.validation.json new file mode 100644 index 0000000000000..db9c5158538e7 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_mappings.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { + "last_validated_date": "2025-04-15T13:05:52+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { + "last_validated_date": "2025-04-15T13:08:27+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { + "last_validated_date": "2025-04-15T13:07:01+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { + "last_validated_date": "2025-04-15T13:15:54+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { + "last_validated_date": "2025-04-15T13:04:43+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { + "last_validated_date": "2025-04-15T13:03:18+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_parameters.py b/tests/aws/services/cloudformation/test_change_set_parameters.py similarity index 94% rename from tests/aws/services/cloudformation/v2/test_change_set_parameters.py rename to tests/aws/services/cloudformation/test_change_set_parameters.py index ac04661b2ba8d..0189be856cf92 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_parameters.py +++ b/tests/aws/services/cloudformation/test_change_set_parameters.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_parameters.snapshot.json b/tests/aws/services/cloudformation/test_change_set_parameters.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_parameters.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_parameters.snapshot.json index 4d0c44f81f248..cdb8fb837e17a 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_parameters.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_parameters.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { "recorded-date": "17-04-2025, 15:35:43", "recorded-content": { "create-change-set-1": { @@ -481,7 +481,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { "recorded-date": "17-04-2025, 15:39:55", "recorded-content": { "create-change-set-1": { @@ -963,7 +963,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { "recorded-date": "17-04-2025, 15:44:25", "recorded-content": { "create-change-set-1": { @@ -1445,7 +1445,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { "recorded-date": "17-04-2025, 15:46:46", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_parameters.validation.json b/tests/aws/services/cloudformation/test_change_set_parameters.validation.json new file mode 100644 index 0000000000000..36c85e0e750ce --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_parameters.validation.json @@ -0,0 +1,14 @@ +{ + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { + "last_validated_date": "2025-04-17T15:35:43+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { + "last_validated_date": "2025-04-17T15:46:46+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { + "last_validated_date": "2025-04-17T15:39:55+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { + "last_validated_date": "2025-04-17T15:44:24+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_ref.py b/tests/aws/services/cloudformation/test_change_set_ref.py similarity index 97% rename from tests/aws/services/cloudformation/v2/test_change_set_ref.py rename to tests/aws/services/cloudformation/test_change_set_ref.py index 3785e861094f2..b3543d82763a2 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_ref.py +++ b/tests/aws/services/cloudformation/test_change_set_ref.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_ref.snapshot.json b/tests/aws/services/cloudformation/test_change_set_ref.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_ref.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_ref.snapshot.json index d6aac38ddd772..00d1b512a7dc4 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_ref.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_ref.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { "recorded-date": "08-04-2025, 15:22:38", "recorded-content": { "create-change-set-1": { @@ -366,7 +366,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { "recorded-date": "08-04-2025, 15:36:44", "recorded-content": { "create-change-set-1": { @@ -825,7 +825,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { "recorded-date": "08-04-2025, 15:45:54", "recorded-content": { "create-change-set-1": { @@ -1356,7 +1356,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { "recorded-date": "08-04-2025, 15:51:05", "recorded-content": { "create-change-set-1": { @@ -1815,7 +1815,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { "recorded-date": "08-04-2025, 16:00:20", "recorded-content": { "create-change-set-1": { @@ -2441,7 +2441,7 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { "recorded-date": "19-05-2025, 10:22:18", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_ref.validation.json b/tests/aws/services/cloudformation/test_change_set_ref.validation.json new file mode 100644 index 0000000000000..f7d5b65ead975 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_ref.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { + "last_validated_date": "2025-04-08T15:36:44+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { + "last_validated_date": "2025-04-08T15:45:54+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { + "last_validated_date": "2025-04-08T15:51:05+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { + "last_validated_date": "2025-04-08T16:00:20+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { + "last_validated_date": "2025-04-08T15:22:37+00:00" + }, + "tests/aws/services/cloudformation/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { + "last_validated_date": "2025-05-19T10:22:18+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_values.py b/tests/aws/services/cloudformation/test_change_set_values.py similarity index 89% rename from tests/aws/services/cloudformation/v2/test_change_set_values.py rename to tests/aws/services/cloudformation/test_change_set_values.py index 90084441dd4cb..7bae300a71cd2 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_values.py +++ b/tests/aws/services/cloudformation/test_change_set_values.py @@ -1,15 +1,11 @@ -import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import long_uid -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "per-resource-events..*", @@ -17,7 +13,6 @@ # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", diff --git a/tests/aws/services/cloudformation/v2/test_change_set_values.snapshot.json b/tests/aws/services/cloudformation/test_change_set_values.snapshot.json similarity index 99% rename from tests/aws/services/cloudformation/v2/test_change_set_values.snapshot.json rename to tests/aws/services/cloudformation/test_change_set_values.snapshot.json index c2b398a920fc4..10515aca705a3 100644 --- a/tests/aws/services/cloudformation/v2/test_change_set_values.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_set_values.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { + "tests/aws/services/cloudformation/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { "recorded-date": "23-05-2025, 17:56:06", "recorded-content": { "create-change-set-1": { diff --git a/tests/aws/services/cloudformation/test_change_set_values.validation.json b/tests/aws/services/cloudformation/test_change_set_values.validation.json new file mode 100644 index 0000000000000..4d140798dd566 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_set_values.validation.json @@ -0,0 +1,5 @@ +{ + "tests/aws/services/cloudformation/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { + "last_validated_date": "2025-05-23T17:56:06+00:00" + } +} diff --git a/tests/aws/services/cloudformation/v2/test_change_sets.py b/tests/aws/services/cloudformation/test_change_sets.py similarity index 79% rename from tests/aws/services/cloudformation/v2/test_change_sets.py rename to tests/aws/services/cloudformation/test_change_sets.py index 20ef3e331d59e..a98d8d8e26ec3 100644 --- a/tests/aws/services/cloudformation/v2/test_change_sets.py +++ b/tests/aws/services/cloudformation/test_change_sets.py @@ -3,34 +3,24 @@ import pytest from localstack_snapshot.snapshots.transformer import RegexTransformer +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider from localstack.aws.connect import ServiceLevelClientFactory -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import short_uid -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -@pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Requires the V2 engine" -) +@skip_if_v1_provider("Requires the V2 engine") @markers.snapshot.skip_snapshot_verify( paths=[ "delete-describe..*", # # Before/After Context "$..Capabilities", - "$..NotificationARNs", "$..IncludeNestedStacks", "$..Scope", "$..Details", "$..Parameters", - "$..Replacement", "$..PolicyAction", "$..PhysicalResourceId", ] @@ -724,78 +714,225 @@ def test_base_mapping_scenarios( ): capture_update_process(snapshot, template_1, template_2) + @markers.aws.validated + def test_single_resource_static_update( + self, aws_client: ServiceLevelClientFactory, snapshot, cleanups + ): + snapshot.add_transformer(snapshot.transform.cloudformation_api()) + parameter_name = f"parameter-{short_uid()}" + value1 = "foo" + value2 = "bar" + + t1 = { + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Name": parameter_name, + "Type": "String", + "Value": value1, + }, + }, + }, + } + + stack_name = f"stack-{short_uid()}" + change_set_name = f"cs-{short_uid()}" + cs_result = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(t1), + ChangeSetType="CREATE", + ) + cs_id = cs_result["Id"] + stack_id = cs_result["StackId"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) + cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) + + describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) + snapshot.match("describe-1", describe_result) + + aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) + aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) + + parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] + snapshot.match("parameter-1", parameter) + t2 = copy.deepcopy(t1) + t2["Resources"]["MyParameter"]["Properties"]["Value"] = value2 + + change_set_name = f"cs-{short_uid()}" + cs_result = aws_client.cloudformation.create_change_set( + StackName=stack_name, + ChangeSetName=change_set_name, + TemplateBody=json.dumps(t2), + ) + cs_id = cs_result["Id"] + aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) + + describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) + snapshot.match("describe-2", describe_result) + + aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) + aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack_id) + + parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] + snapshot.match("parameter-2", parameter) + + +@skip_if_v1_provider("Not supported with v1 engine") @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ + "delete-describe..*", + # + # Before/After Context "$..Capabilities", "$..IncludeNestedStacks", - "$..NotificationARNs", + "$..Scope", + "$..Details", "$..Parameters", - "$..Changes..ResourceChange.Details", - "$..Changes..ResourceChange.Scope", - "$..Changes..ResourceChange.PhysicalResourceId", - "$..Changes..ResourceChange.Replacement", + "$..Replacement", + "$..PolicyAction", + "$..PhysicalResourceId", ] ) -def test_single_resource_static_update(aws_client: ServiceLevelClientFactory, snapshot, cleanups): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - parameter_name = f"parameter-{short_uid()}" - value1 = "foo" - value2 = "bar" +def test_dynamic_ssm_parameter_lookup( + snapshot, + aws_client: ServiceLevelClientFactory, + aws_client_no_retry: ServiceLevelClientFactory, + cleanups, + create_parameter, + capture_update_process, +): + """ + Test reading parameter values from SSM works correctly""" + parameter_name = f"param-{short_uid()}" + value1 = f"value-1-{short_uid()}" + value2 = f"value-2-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("OutputValue"), + snapshot.transform.regex(parameter_name, ""), + snapshot.transform.regex(value1, ""), + snapshot.transform.regex(value2, ""), + ] + + snapshot.transform.cloudformation_api(), + ) - t1 = { + create_parameter(Name=parameter_name, Value=value1, Type="String") + + template = { + "Parameters": { + "InputValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, "Resources": { - "MyParameter": { + "DerivedParameter": { "Type": "AWS::SSM::Parameter", "Properties": { - "Name": parameter_name, "Type": "String", - "Value": value1, + "Value": {"Ref": "InputValue"}, }, }, }, + "Outputs": { + "DerivedParameterName": { + "Value": {"Ref": "DerivedParameter"}, + }, + }, } - stack_name = f"stack-{short_uid()}" - change_set_name = f"cs-{short_uid()}" - cs_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=json.dumps(t1), - ChangeSetType="CREATE", + def update_parameter_value(): + aws_client.ssm.put_parameter(Name=parameter_name, Value=value2, Overwrite=True) + + capture_update_process( + snapshot, + template, + template, + p1={"InputValue": parameter_name}, + p2={"InputValue": parameter_name}, + custom_update_step=update_parameter_value, ) - cs_id = cs_result["Id"] - stack_id = cs_result["StackId"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_id)) - describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) - snapshot.match("describe-1", describe_result) - aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) +@skip_if_v1_provider("Not supported with v1 engine") +@markers.aws.validated +@markers.snapshot.skip_snapshot_verify( + paths=[ + "delete-describe..*", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..Replacement", + "$..PolicyAction", + "$..PhysicalResourceId", + ] +) +def test_dynamic_ssm_parameter_lookup_no_change( + snapshot, + aws_client: ServiceLevelClientFactory, + aws_client_no_retry: ServiceLevelClientFactory, + cleanups, + create_parameter, + capture_update_process, +): + """ + Test reading parameter values from SSM works correctly""" + parameter_name = f"param-{short_uid()}" + parameter_value = f"value-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("OutputValue"), + snapshot.transform.regex(parameter_name, ""), + snapshot.transform.regex(parameter_value, ""), + ] + + snapshot.transform.cloudformation_api(), + ) - parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] - snapshot.match("parameter-1", parameter) + create_parameter(Name=parameter_name, Value=parameter_value, Type="String") + t1 = { + "Parameters": { + "InputValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "DerivedParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "InputValue"}, + }, + }, + }, + "Outputs": { + "DerivedParameterName": { + "Value": {"Ref": "DerivedParameter"}, + }, + }, + } t2 = copy.deepcopy(t1) - t2["Resources"]["MyParameter"]["Properties"]["Value"] = value2 + t2["Resources"]["AnotherParameter"] = { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": "new parameter", + }, + } - change_set_name = f"cs-{short_uid()}" - cs_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=json.dumps(t2), + capture_update_process( + snapshot, + t1, + t2, + p1={"InputValue": parameter_name}, + p2={"InputValue": parameter_name}, ) - cs_id = cs_result["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait(ChangeSetName=cs_id) - - describe_result = aws_client.cloudformation.describe_change_set(ChangeSetName=cs_id) - snapshot.match("describe-2", describe_result) - - aws_client.cloudformation.execute_change_set(ChangeSetName=cs_id) - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack_id) - - parameter = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"] - snapshot.match("parameter-2", parameter) diff --git a/tests/aws/services/cloudformation/v2/test_change_sets.snapshot.json b/tests/aws/services/cloudformation/test_change_sets.snapshot.json similarity index 59% rename from tests/aws/services/cloudformation/v2/test_change_sets.snapshot.json rename to tests/aws/services/cloudformation/test_change_sets.snapshot.json index 66b1117810662..53751bc70eb68 100644 --- a/tests/aws/services/cloudformation/v2/test_change_sets.snapshot.json +++ b/tests/aws/services/cloudformation/test_change_sets.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_change_sets.py::test_single_resource_static_update": { + "tests/aws/services/cloudformation/test_change_sets.py::test_single_resource_static_update": { "recorded-date": "18-03-2025, 16:52:36", "recorded-content": { "describe-1": { @@ -94,8 +94,8 @@ } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { - "recorded-date": "18-06-2025, 19:04:55", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { + "recorded-date": "05-08-2025, 14:15:57", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -341,7 +341,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -355,7 +355,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -366,6 +366,34 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Stack": [ @@ -403,13 +431,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { - "recorded-date": "18-06-2025, 19:06:59", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { + "recorded-date": "05-08-2025, 14:17:58", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -599,9 +641,8 @@ }, "Details": [ { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -613,8 +654,9 @@ } }, { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -627,7 +669,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -696,7 +738,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -760,7 +802,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -774,7 +816,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -785,36 +827,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-OkuGHMW4ltfZ", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gE4nEU6CQEj9", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -852,13 +936,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { - "recorded-date": "18-06-2025, 19:09:04", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { + "recorded-date": "05-08-2025, 14:20:03", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -1080,9 +1178,8 @@ }, "Details": [ { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1094,8 +1191,9 @@ } }, { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1108,7 +1206,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1193,7 +1291,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1275,7 +1373,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1289,7 +1387,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1300,36 +1398,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-lZ25tyPMdFIo", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-gTdrbz5CtU3q", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -1367,13 +1507,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { - "recorded-date": "18-06-2025, 19:11:09", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { + "recorded-date": "05-08-2025, 14:22:08", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -1563,8 +1717,9 @@ }, "Details": [ { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1576,9 +1731,8 @@ } }, { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -1591,7 +1745,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1660,7 +1814,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -1724,7 +1878,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1738,7 +1892,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -1749,36 +1903,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-QY7XaFoB4kQc", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-tSeCu89dWGbq", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -1816,13 +2012,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { - "recorded-date": "18-06-2025, 19:13:17", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { + "recorded-date": "05-08-2025, 14:24:09", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -2044,9 +2254,8 @@ }, "Details": [ { - "CausingEntity": "Foo.TopicName", - "ChangeSource": "ResourceAttribute", - "Evaluation": "Static", + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -2058,8 +2267,9 @@ } }, { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", + "CausingEntity": "Foo.TopicName", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", "Target": { "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", "Attribute": "Properties", @@ -2072,7 +2282,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2157,7 +2367,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2239,7 +2449,7 @@ "Foo": [ { "LogicalResourceId": "Foo", - "PhysicalResourceId": "", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -2253,7 +2463,7 @@ }, { "LogicalResourceId": "Foo", - "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" @@ -2264,36 +2474,78 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" - } - ], - "Parameter": [ + }, { - "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::SSM::Parameter", + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" }, { - "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SSM::Parameter", + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-1", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", "Timestamp": "timestamp" }, { - "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", - "ResourceStatus": "UPDATE_IN_PROGRESS", + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Foo", + "PhysicalResourceId": "arn::sns::111111111111:topic-name-2", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SNS::Topic", + "Timestamp": "timestamp" + } + ], + "Parameter": [ + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-tGkdmdoGLN1m", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-vESR0MLTgKRp", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -2331,13 +2583,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { - "recorded-date": "18-06-2025, 19:13:55", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { + "recorded-date": "05-08-2025, 14:24:45", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -2585,33 +2851,61 @@ "Bucket": [ { "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", + "PhysicalResourceId": "-bucket-i4eammke7jqd", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::S3::Bucket", "Timestamp": "timestamp" }, { "LogicalResourceId": "Bucket", - "PhysicalResourceId": "-bucket-rvkyycxytnfz", + "PhysicalResourceId": "-bucket-i4eammke7jqd", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::S3::Bucket", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Bucket", + "PhysicalResourceId": "-bucket-i4eammke7jqd", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Bucket", + "PhysicalResourceId": "-bucket-i4eammke7jqd", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": "timestamp" } ], "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-ytEGT7JWBrkx", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-PvfZzrj2a2uP", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -2649,13 +2943,27 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { - "recorded-date": "18-06-2025, 19:14:21", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { + "recorded-date": "05-08-2025, 14:25:08", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -2827,7 +3135,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2886,7 +3194,7 @@ } ], "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -2968,31 +3276,45 @@ "Parameter": [ { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "Parameter", - "PhysicalResourceId": "CFN-Parameter-BNuHBis1ysn1", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter", + "PhysicalResourceId": "CFN-Parameter-n1KyWPxiTuNB", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -3030,21 +3352,35 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": { - "recorded-date": "18-06-2025, 19:14:21", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": { + "recorded-date": "05-08-2025, 14:25:08", "recorded-content": {} }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": { - "recorded-date": "18-06-2025, 19:14:21", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": { + "recorded-date": "05-08-2025, 14:25:08", "recorded-content": {} }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { - "recorded-date": "18-06-2025, 19:14:47", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { + "recorded-date": "05-08-2025, 14:25:30", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -3295,33 +3631,61 @@ "SSMParameter1": [ { "LogicalResourceId": "SSMParameter1", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "SSMParameter1", - "PhysicalResourceId": "CFN-SSMParameter1-YEPpTp1eTqmV", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter1", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter1", + "PhysicalResourceId": "CFN-SSMParameter1-53MomZeWdc3v", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "SSMParameter2": [ { "LogicalResourceId": "SSMParameter2", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "SSMParameter2", - "PhysicalResourceId": "CFN-SSMParameter2-Cy9JferYSQvx", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter2", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "SSMParameter2", + "PhysicalResourceId": "CFN-SSMParameter2-9IuT0RFdItlN", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -3359,20 +3723,34 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { - "recorded-date": "18-06-2025, 19:15:20", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { + "recorded-date": "05-08-2025, 14:26:05", "recorded-content": { "before-value": "", "after-value": "" } }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { - "recorded-date": "18-06-2025, 19:15:45", + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { + "recorded-date": "05-08-2025, 14:26:31", "recorded-content": { "create-change-set-1": { "Id": "arn::cloudformation::111111111111:changeSet/", @@ -3512,7 +3890,7 @@ } ], "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -3555,7 +3933,7 @@ } ], "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "Replacement": "False", "ResourceType": "AWS::SSM::Parameter", "Scope": [ @@ -3619,31 +3997,45 @@ "MySSMParameter": [ { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "CREATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "CREATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "UPDATE_IN_PROGRESS", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" }, { "LogicalResourceId": "MySSMParameter", - "PhysicalResourceId": "CFN-MySSMParameter-yMAYpjhjWvEz", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::SSM::Parameter", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MySSMParameter", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MySSMParameter", + "PhysicalResourceId": "CFN-MySSMParameter-EcezJ6ETXORO", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" } ], "Stack": [ @@ -3681,6 +4073,1801 @@ "ResourceStatus": "UPDATE_COMPLETE", "ResourceType": "AWS::CloudFormation::Stack", "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_single_resource_static_update": { + "recorded-date": "05-08-2025, 14:26:48", + "recorded-content": { + "describe-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "parameter-1": { + "ARN": "arn::ssm::111111111111:parameter/", + "DataType": "text", + "LastModifiedDate": "datetime", + "Name": "", + "Type": "String", + "Value": "foo", + "Version": 1 + }, + "describe-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "parameter-2": { + "ARN": "arn::ssm::111111111111:parameter/", + "DataType": "text", + "LastModifiedDate": "datetime", + "Name": "", + "Type": "String", + "Value": "bar", + "Version": 2 + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": { + "recorded-date": "31-07-2025, 14:01:52", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "topic-name", + "Type": "String", + "Description": "original" + } + }, + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "topic-name", + "Type": "String", + "Description": "changed" + } + }, + "BeforeContext": { + "Properties": { + "Value": "topic-name", + "Type": "String", + "Description": "original" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "changed", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "original", + "Name": "Description", + "Path": "/Properties/Description", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Description", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "Parameter1.Value", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Dynamic", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-IUxUU6IW7X39", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter1": [ + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "CFN-Parameter1-ZUgkraElDaDN", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Parameter2": [ + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-IUxUU6IW7X39", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": { + "recorded-date": "31-07-2025, 14:10:27", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "value", + "Type": "String", + "Name": "parameter-1-name" + } + }, + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter1", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "Parameter2", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "value", + "Type": "String", + "Name": "parameter-2-name" + } + }, + "BeforeContext": { + "Properties": { + "Value": "value", + "Type": "String", + "Name": "parameter-1-name" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "parameter-2-name", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "parameter-1-name", + "Name": "Name", + "Path": "/Properties/Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "value", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Dynamic", + "Target": { + "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "value", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + }, + { + "CausingEntity": "Parameter1.Value", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", + "Target": { + "AfterValue": "{{changeSet:KNOWN_AFTER_APPLY}}", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "value", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-DAxvYPlPS8Me", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Name", + "RequiresRecreation": "Always" + } + } + ], + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "PolicyAction": "ReplaceAndDelete", + "Replacement": "True", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + }, + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "CausingEntity": "Parameter1.Value", + "ChangeSource": "ResourceAttribute", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-DAxvYPlPS8Me", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "Parameter1": [ + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-1-name", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter1", + "PhysicalResourceId": "parameter-2-name", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Parameter2": [ + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "Parameter2", + "PhysicalResourceId": "CFN-Parameter2-DAxvYPlPS8Me", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup": { + "recorded-date": "05-08-2025, 22:24:46", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "DerivedParameter": [ + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup_no_change": { + "recorded-date": "05-08-2025, 22:26:18", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "DerivedParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "arn::cloudformation::111111111111:stack//", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "new parameter", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "AnotherParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "AnotherParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Outputs": [ + { + "OutputKey": "DerivedParameterName", + "OutputValue": "" + } + ], + "Parameters": [ + { + "ParameterKey": "InputValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "arn::cloudformation::111111111111:stack//", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "AnotherParameter": [ + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "AnotherParameter", + "PhysicalResourceId": "CFN-AnotherParameter-V5IPg431Z6Rc", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "DerivedParameter": [ + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "DerivedParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" } ] } diff --git a/tests/aws/services/cloudformation/test_change_sets.validation.json b/tests/aws/services/cloudformation/test_change_sets.validation.json new file mode 100644 index 0000000000000..4044808322c88 --- /dev/null +++ b/tests/aws/services/cloudformation/test_change_sets.validation.json @@ -0,0 +1,140 @@ +{ + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { + "last_validated_date": "2025-08-05T14:25:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 22.53, + "teardown": 0.13, + "total": 22.66 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { + "last_validated_date": "2025-08-05T14:25:30+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.45, + "teardown": 0.15, + "total": 21.6 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { + "last_validated_date": "2025-08-05T14:26:31+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 25.72, + "teardown": 0.13, + "total": 25.85 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { + "last_validated_date": "2025-08-05T14:24:45+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 36.52, + "teardown": 0.13, + "total": 36.65 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { + "last_validated_date": "2025-08-05T14:15:57+00:00", + "durations_in_seconds": { + "setup": 1.0, + "call": 115.36, + "teardown": 0.15, + "total": 116.51 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { + "last_validated_date": "2025-08-05T14:17:58+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 120.52, + "teardown": 0.13, + "total": 120.65 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { + "last_validated_date": "2025-08-05T14:26:05+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 28.7, + "teardown": 6.54, + "total": 35.24 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { + "last_validated_date": "2025-08-05T14:24:09+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 120.64, + "teardown": 0.14, + "total": 120.78 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { + "last_validated_date": "2025-08-05T14:22:08+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 124.78, + "teardown": 0.14, + "total": 124.92 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { + "last_validated_date": "2025-08-05T14:20:03+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 124.89, + "teardown": 0.18, + "total": 125.07 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::TestCaptureUpdateProcess::test_single_resource_static_update": { + "last_validated_date": "2025-08-05T14:26:48+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 16.65, + "teardown": 0.21, + "total": 16.86 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup": { + "last_validated_date": "2025-08-05T22:24:46+00:00", + "durations_in_seconds": { + "setup": 1.11, + "call": 26.5, + "teardown": 0.36, + "total": 27.97 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_dynamic_ssm_parameter_lookup_no_change": { + "last_validated_date": "2025-08-05T22:26:18+00:00", + "durations_in_seconds": { + "setup": 1.02, + "call": 22.04, + "teardown": 0.39, + "total": 23.45 + } + }, + "tests/aws/services/cloudformation/test_change_sets.py::test_single_resource_static_update": { + "last_validated_date": "2025-03-18T16:52:35+00:00" + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": { + "last_validated_date": "2025-07-31T14:10:27+00:00", + "durations_in_seconds": { + "setup": 1.23, + "call": 30.31, + "teardown": 0.12, + "total": 31.66 + } + }, + "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": { + "last_validated_date": "2025-07-31T14:01:52+00:00", + "durations_in_seconds": { + "setup": 1.71, + "call": 33.35, + "teardown": 0.13, + "total": 35.19 + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_list_stacks.py b/tests/aws/services/cloudformation/test_list_stacks.py similarity index 92% rename from tests/aws/services/cloudformation/v2/test_list_stacks.py rename to tests/aws/services/cloudformation/test_list_stacks.py index dcbc37af6c39f..5c9ec827a10c3 100644 --- a/tests/aws/services/cloudformation/v2/test_list_stacks.py +++ b/tests/aws/services/cloudformation/test_list_stacks.py @@ -1,8 +1,12 @@ import json +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider + from localstack.testing.pytest import markers from localstack.utils.strings import short_uid +pytestmark = skip_if_v1_provider(reason="Requires the V2 engine") + @markers.aws.validated def test_listing_stacks(aws_client, deploy_cfn_template, snapshot): diff --git a/tests/aws/services/cloudformation/v2/test_list_stacks.snapshot.json b/tests/aws/services/cloudformation/test_list_stacks.snapshot.json similarity index 92% rename from tests/aws/services/cloudformation/v2/test_list_stacks.snapshot.json rename to tests/aws/services/cloudformation/test_list_stacks.snapshot.json index 4c66aecdceae3..e5d9e526b9d05 100644 --- a/tests/aws/services/cloudformation/v2/test_list_stacks.snapshot.json +++ b/tests/aws/services/cloudformation/test_list_stacks.snapshot.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_list_stacks.py::test_listing_stacks": { + "tests/aws/services/cloudformation/test_list_stacks.py::test_listing_stacks": { "recorded-date": "24-07-2025, 15:35:20", "recorded-content": { "stacks": [ diff --git a/tests/aws/services/cloudformation/v2/test_list_stacks.validation.json b/tests/aws/services/cloudformation/test_list_stacks.validation.json similarity index 68% rename from tests/aws/services/cloudformation/v2/test_list_stacks.validation.json rename to tests/aws/services/cloudformation/test_list_stacks.validation.json index 089376ba56b59..395ca189c5871 100644 --- a/tests/aws/services/cloudformation/v2/test_list_stacks.validation.json +++ b/tests/aws/services/cloudformation/test_list_stacks.validation.json @@ -1,5 +1,5 @@ { - "tests/aws/services/cloudformation/v2/test_list_stacks.py::test_listing_stacks": { + "tests/aws/services/cloudformation/test_list_stacks.py::test_listing_stacks": { "last_validated_date": "2025-07-24T15:35:33+00:00", "durations_in_seconds": { "setup": 1.21, diff --git a/tests/aws/services/cloudformation/test_template_engine.py b/tests/aws/services/cloudformation/test_template_engine.py index d039307ef5101..83cb90b640f76 100644 --- a/tests/aws/services/cloudformation/test_template_engine.py +++ b/tests/aws/services/cloudformation/test_template_engine.py @@ -7,6 +7,8 @@ import botocore.exceptions import pytest import yaml +from botocore.exceptions import ClientError +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider, skip_if_v2_provider from localstack.aws.api.lambda_ import Runtime from localstack.services.cloudformation.engine.yaml_parser import parse_yaml @@ -112,6 +114,7 @@ def test_base64_sub_and_getatt_functions(self, deploy_cfn_template): converted_string = base64.b64encode(bytes(original_string, "utf-8")).decode("utf-8") assert converted_string == deployed.outputs["Encoded"] + @skip_if_v2_provider("LanguageExtensions") @markers.aws.validated def test_split_length_and_join_functions(self, deploy_cfn_template): template_path = os.path.join( @@ -273,6 +276,16 @@ def test_join_no_value_construct(self, deploy_cfn_template, snapshot, aws_client snapshot.match("join-output", stack.outputs) + @markers.aws.validated + def test_fn_select_has_intrinsic_function(self, deploy_cfn_template, snapshot, aws_client): + stack = deploy_cfn_template( + template_path=os.path.join( + os.path.dirname(__file__), "../../templates/engine/fn_select_fn_mapp.yml" + ) + ) + + snapshot.match("fn-select-fn-map-output", stack.outputs) + class TestImports: @markers.aws.validated @@ -331,10 +344,13 @@ def test_create_stack_with_ssm_parameters( matching = [arn for arn in topic_arns if parameter_value in arn] assert len(matching) == 1 - tags = aws_client.sns.list_tags_for_resource(ResourceArn=matching[0]) - snapshot.match("topic-tags", tags) + tags = aws_client.sns.list_tags_for_resource(ResourceArn=matching[0])["Tags"] + snapshot.match( + "topic-tags", [tag for tag in tags if not tag["Key"].startswith("aws:cloudformation")] + ) @markers.aws.validated + @skip_if_v2_provider("Resolve") def test_resolve_ssm(self, create_parameter, deploy_cfn_template): parameter_key = f"param-key-{short_uid()}" parameter_value = f"param-value-{short_uid()}" @@ -350,6 +366,7 @@ def test_resolve_ssm(self, create_parameter, deploy_cfn_template): topic_name = result.outputs["TopicName"] assert topic_name == parameter_value + @skip_if_v2_provider("Resolve") @markers.aws.validated def test_resolve_ssm_with_version(self, create_parameter, deploy_cfn_template, aws_client): parameter_key = f"param-key-{short_uid()}" @@ -376,6 +393,7 @@ def test_resolve_ssm_with_version(self, create_parameter, deploy_cfn_template, a topic_name = result.outputs["TopicName"] assert topic_name == parameter_value_v1 + @skip_if_v2_provider("Resolve") @markers.aws.needs_fixing def test_resolve_ssm_secure(self, create_parameter, deploy_cfn_template): parameter_key = f"param-key-{short_uid()}" @@ -393,6 +411,31 @@ def test_resolve_ssm_secure(self, create_parameter, deploy_cfn_template): topic_name = result.outputs["TopicName"] assert topic_name == parameter_value + @skip_if_v1_provider("Not supported in the v1 provider") + @markers.aws.validated + def test_resolve_ssm_missing_parameter(self, snapshot, deploy_cfn_template): + template = { + "Parameters": { + "InputValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "InputValue"}, + }, + }, + }, + } + with pytest.raises(ClientError) as exc_info: + deploy_cfn_template( + template=json.dumps(template), parameters={"InputValue": "parameter-does-not-exist"} + ) + snapshot.match("error-response", exc_info.value) + @markers.aws.validated def test_ssm_nested_with_nested_stack(self, s3_create_bucket, deploy_cfn_template, aws_client): """ @@ -428,6 +471,7 @@ def test_ssm_nested_with_nested_stack(self, s3_create_bucket, deploy_cfn_templat assert ssm_parameter == key_value + @skip_if_v2_provider("Resolve", reason="stringlist type not supported yet") @markers.aws.validated def test_create_change_set_with_ssm_parameter_list( self, deploy_cfn_template, aws_client, region_name, account_id, snapshot @@ -626,6 +670,7 @@ def test_macro_deployment( snapshot.match("stack_outputs", stack_with_macro.outputs) snapshot.match("stack_resource_descriptions", description) + @skip_if_v2_provider("Macros") @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ @@ -686,6 +731,9 @@ def test_global_scope( snapshot.add_transformer(snapshot.transform.regex(new_value, "new-value")) snapshot.match("processed_template", processed_template) + @skip_if_v2_provider( + "Transform", reason="as resource property with missing Name and Parameters fields" + ) @markers.aws.validated @pytest.mark.parametrize( "template_to_transform", @@ -746,6 +794,7 @@ def test_snipped_scope( snapshot.match("original_template", original_template) snapshot.match("processed_template", processed_template) + @skip_if_v2_provider("Fn::Transform") @markers.aws.validated def test_attribute_uses_macro(self, deploy_cfn_template, create_lambda_function, aws_client): macro_function_path = os.path.join( @@ -822,6 +871,7 @@ def test_scope_order_and_parameters( ) snapshot.match("processed_template", processed_template) + @skip_if_v2_provider("Transform") @markers.aws.validated @markers.snapshot.skip_snapshot_verify( paths=[ @@ -952,6 +1002,7 @@ def test_validate_lambda_internals( processed_template["TemplateBody"]["Resources"]["Parameter"]["Properties"]["Value"], ) + @skip_if_v2_provider("Transform") @markers.aws.validated def test_to_validate_template_limit_for_macro( self, deploy_cfn_template, create_lambda_function, snapshot, aws_client @@ -1004,6 +1055,7 @@ def test_to_validate_template_limit_for_macro( ) snapshot.match("error_response", response) + @skip_if_v2_provider("Transform") @markers.aws.validated def test_error_pass_macro_as_reference(self, snapshot, aws_client): """ @@ -1025,12 +1077,13 @@ def test_error_pass_macro_as_reference(self, snapshot, aws_client): ) snapshot.match("error", ex.value.response) + @skip_if_v2_provider("Transform") @markers.aws.validated def test_functions_and_references_during_transformation( self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client ): """ - This tests shows the state of instrinsic functions during the execution of the macro + This tests shows the state of intrinsic functions during the execution of the macro """ macro_function_path = os.path.join( os.path.dirname(__file__), "../../templates/macros/print_references.py" @@ -1075,6 +1128,7 @@ def test_functions_and_references_during_transformation( processed_template["TemplateBody"]["Resources"]["Parameter"]["Properties"]["Value"], ) + @skip_if_v2_provider("Transform") @pytest.mark.parametrize( "macro_function", [ diff --git a/tests/aws/services/cloudformation/test_template_engine.snapshot.json b/tests/aws/services/cloudformation/test_template_engine.snapshot.json index da52914bdd544..802ae17c4b8c5 100644 --- a/tests/aws/services/cloudformation/test_template_engine.snapshot.json +++ b/tests/aws/services/cloudformation/test_template_engine.snapshot.json @@ -509,7 +509,7 @@ } }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "recorded-date": "15-01-2023, 17:54:23", + "recorded-date": "06-08-2025, 10:16:50", "recorded-content": { "stack-details": { "Capabilities": [ @@ -539,18 +539,12 @@ "StackStatus": "CREATE_COMPLETE", "Tags": [] }, - "topic-tags": { - "Tags": [ - { - "Key": "param-value", - "Value": "param " - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 + "topic-tags": [ + { + "Key": "param-value", + "Value": "param " } - } + ] } }, "tests/aws/services/cloudformation/test_template_engine.py::TestMacros::test_macro_deployment": { @@ -683,5 +677,19 @@ "JoinWithNoValue": "Sample" } } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_fn_select_has_intrinsic_function": { + "recorded-date": "05-08-2025, 18:44:01", + "recorded-content": { + "fn-select-fn-map-output": { + "ParameterValue": "true" + } + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_missing_parameter": { + "recorded-date": "06-08-2025, 09:34:07", + "recorded-content": { + "error-response": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Parameter InputValue should either have input value or default value" + } } } diff --git a/tests/aws/services/cloudformation/test_template_engine.validation.json b/tests/aws/services/cloudformation/test_template_engine.validation.json index e0bbb0be7e342..c95a177e56bbb 100644 --- a/tests/aws/services/cloudformation/test_template_engine.validation.json +++ b/tests/aws/services/cloudformation/test_template_engine.validation.json @@ -8,6 +8,15 @@ "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": { "last_validated_date": "2024-06-20T20:41:15+00:00" }, + "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_fn_select_has_intrinsic_function": { + "last_validated_date": "2025-08-05T18:44:05+00:00", + "durations_in_seconds": { + "setup": 0.23, + "call": 10.41, + "teardown": 4.4, + "total": 15.04 + } + }, "tests/aws/services/cloudformation/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function": { "last_validated_date": "2024-04-03T07:12:29+00:00" }, @@ -93,7 +102,22 @@ "last_validated_date": "2024-08-08T21:21:23+00:00" }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "last_validated_date": "2023-01-15T16:54:23+00:00" + "last_validated_date": "2025-08-06T10:17:40+00:00", + "durations_in_seconds": { + "setup": 0.86, + "call": 10.93, + "teardown": 49.95, + "total": 61.74 + } + }, + "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_resolve_ssm_missing_parameter": { + "last_validated_date": "2025-08-06T09:34:07+00:00", + "durations_in_seconds": { + "setup": 0.87, + "call": 0.29, + "teardown": 0.0, + "total": 1.16 + } }, "tests/aws/services/cloudformation/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": { "last_validated_date": "2024-07-16T16:38:43+00:00" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/__init__.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py deleted file mode 100644 index dbfbbd279d989..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py +++ /dev/null @@ -1,1243 +0,0 @@ -import copy -import json -import os.path - -import pytest -from botocore.exceptions import ClientError -from tests.aws.services.cloudformation.api.test_stacks import ( - MINIMAL_TEMPLATE, -) - -from localstack.aws.connect import ServiceLevelClientFactory -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.cloudformation_utils import ( - load_template_file, - load_template_raw, - render_template, -) -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import ShortCircuitWaitException, poll_condition, wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestUpdates: - @markers.aws.validated - def test_simple_update_single_resource( - self, aws_client: ServiceLevelClientFactory, deploy_cfn_template - ): - value1 = "foo" - value2 = "bar" - stack_name = f"stack-{short_uid()}" - - t1 = { - "Resources": { - "MyParameter": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": value1, - }, - }, - }, - "Outputs": { - "ParameterName": { - "Value": {"Ref": "MyParameter"}, - }, - }, - } - - res = deploy_cfn_template(stack_name=stack_name, template=json.dumps(t1), is_update=False) - parameter_name = res.outputs["ParameterName"] - - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value1 - - t2 = copy.deepcopy(t1) - t2["Resources"]["MyParameter"]["Properties"]["Value"] = value2 - - deploy_cfn_template(stack_name=stack_name, template=json.dumps(t2), is_update=True) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value2 - - res.destroy() - - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Not working in v2 yet" - ) - @markers.aws.validated - def test_simple_update_two_resources( - self, aws_client: ServiceLevelClientFactory, deploy_cfn_template - ): - parameter_name = "my-parameter" - value1 = "foo" - value2 = "bar" - stack_name = f"stack-{short_uid()}" - - t1 = { - "Resources": { - "MyParameter1": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": value1, - }, - }, - "MyParameter2": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Name": parameter_name, - "Type": "String", - "Value": {"Fn::GetAtt": ["MyParameter1", "Value"]}, - }, - }, - }, - } - - res = deploy_cfn_template(stack_name=stack_name, template=json.dumps(t1), is_update=False) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value1 - - t2 = copy.deepcopy(t1) - t2["Resources"]["MyParameter1"]["Properties"]["Value"] = value2 - - deploy_cfn_template(stack_name=stack_name, template=json.dumps(t2), is_update=True) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value2 - - res.destroy() - - @markers.aws.validated - # TODO: the error response is incorrect, however the test is otherwise validated and raises - # an error because the SSM parameter has been deleted (removed from the stack). - @markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) - @pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), reason="Test fails with the old engine" - ) - def test_deleting_resource( - self, aws_client: ServiceLevelClientFactory, deploy_cfn_template, snapshot - ): - parameter_name = "my-parameter" - value1 = "foo" - - t1 = { - "Resources": { - "MyParameter1": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": value1, - }, - }, - "MyParameter2": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Name": parameter_name, - "Type": "String", - "Value": {"Fn::GetAtt": ["MyParameter1", "Value"]}, - }, - }, - }, - } - - stack = deploy_cfn_template(template=json.dumps(t1)) - found_value = aws_client.ssm.get_parameter(Name=parameter_name)["Parameter"]["Value"] - assert found_value == value1 - - t2 = copy.deepcopy(t1) - del t2["Resources"]["MyParameter2"] - - deploy_cfn_template(stack_name=stack.stack_name, template=json.dumps(t2), is_update=True) - with pytest.raises(ClientError) as exc_info: - aws_client.ssm.get_parameter(Name=parameter_name) - - snapshot.match("get-parameter-error", exc_info.value.response) - - -@markers.aws.validated -def test_create_change_set_without_parameters( - cleanup_stacks, cleanup_changesets, is_change_set_created_and_available, aws_client -): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - # make sure the change set wasn't executed (which would create a topic) - topics = aws_client.sns.list_topics() - topic_arns = [x["TopicArn"] for x in topics["Topics"]] - assert not any("sns-topic-simple" in arn for arn in topic_arns) - # stack is initially in REVIEW_IN_PROGRESS state. only after executing the change_set will it change its status - stack_response = aws_client.cloudformation.describe_stacks(StackName=stack_id) - assert stack_response["Stacks"][0]["StackStatus"] == "REVIEW_IN_PROGRESS" - - # Change set can now either be already created/available or it is pending/unavailable - wait_until( - is_change_set_created_and_available(change_set_id), 2, 10, strategy="exponential" - ) - describe_response = aws_client.cloudformation.describe_change_set( - ChangeSetName=change_set_id - ) - - assert describe_response["ChangeSetName"] == change_set_name - assert describe_response["ChangeSetId"] == change_set_id - assert describe_response["StackId"] == stack_id - assert describe_response["StackName"] == stack_name - assert describe_response["ExecutionStatus"] == "AVAILABLE" - assert describe_response["Status"] == "CREATE_COMPLETE" - changes = describe_response["Changes"] - assert len(changes) == 1 - assert changes[0]["Type"] == "Resource" - assert changes[0]["ResourceChange"]["Action"] == "Add" - assert changes[0]["ResourceChange"]["ResourceType"] == "AWS::SNS::Topic" - assert changes[0]["ResourceChange"]["LogicalResourceId"] == "topic123" - finally: - cleanup_stacks([stack_id]) - cleanup_changesets([change_set_id]) - - -# TODO: implement -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="Not properly implemented") -@markers.aws.validated -def test_create_change_set_update_without_parameters( - cleanup_stacks, - cleanup_changesets, - is_change_set_created_and_available, - is_change_set_finished, - snapshot, - aws_client, -): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - """after creating a stack via a CREATE change set we send an UPDATE change set changing the SNS topic name""" - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - change_set_name2 = f"change-set-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - snapshot.match("create_change_set", response) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - # Change set can now either be already created/available or it is pending/unavailable - wait_until(is_change_set_created_and_available(change_set_id)) - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - wait_until(is_change_set_finished(change_set_id)) - template = load_template_raw(template_path) - - update_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name2, - TemplateBody=template.replace("sns-topic-simple", "sns-topic-simple-2"), - ChangeSetType="UPDATE", - ) - assert wait_until(is_change_set_created_and_available(update_response["Id"])) - snapshot.match( - "describe_change_set", - aws_client.cloudformation.describe_change_set(ChangeSetName=update_response["Id"]), - ) - snapshot.match( - "list_change_set", aws_client.cloudformation.list_change_sets(StackName=stack_name) - ) - - describe_response = aws_client.cloudformation.describe_change_set( - ChangeSetName=update_response["Id"] - ) - changes = describe_response["Changes"] - assert len(changes) == 1 - assert changes[0]["Type"] == "Resource" - change = changes[0]["ResourceChange"] - assert change["Action"] == "Modify" - assert change["ResourceType"] == "AWS::SNS::Topic" - assert change["LogicalResourceId"] == "topic123" - assert "sns-topic-simple" in change["PhysicalResourceId"] - assert change["Replacement"] == "True" - assert "Properties" in change["Scope"] - assert len(change["Details"]) == 1 - assert change["Details"][0]["Target"]["Name"] == "TopicName" - assert change["Details"][0]["Target"]["RequiresRecreation"] == "Always" - finally: - cleanup_changesets(changesets=[change_set_id]) - cleanup_stacks(stacks=[stack_id]) - - -# def test_create_change_set_with_template_url(): -# pass - - -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="change set type not implemented") -@markers.aws.validated -def test_create_change_set_create_existing(cleanup_changesets, cleanup_stacks, aws_client): - """tries to create an already existing stack""" - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=change_set_id - ) - stack_id = response["StackId"] - assert change_set_id - assert stack_id - try: - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_id) - - with pytest.raises(Exception) as ex: - change_set_name2 = f"change-set-{short_uid()}" - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name2, - TemplateBody=load_template_raw("sns_topic_simple.yaml"), - ChangeSetType="CREATE", - ) - assert ex is not None - finally: - cleanup_changesets([change_set_id]) - cleanup_stacks([stack_id]) - - -@markers.aws.validated -def test_create_change_set_update_nonexisting(aws_client): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - - with pytest.raises(Exception) as ex: - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="UPDATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - err = ex.value.response["Error"] - assert err["Code"] == "ValidationError" - assert "does not exist" in err["Message"] - - -@markers.aws.validated -def test_create_change_set_invalid_params(aws_client): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - with pytest.raises(ClientError) as ex: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="INVALID", - ) - err = ex.value.response["Error"] - assert err["Code"] == "ValidationError" - - -@markers.aws.validated -def test_create_change_set_missing_stackname(aws_client): - """in this case boto doesn't even let us send the request""" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - with pytest.raises(Exception): - aws_client.cloudformation.create_change_set( - StackName="", - ChangeSetName=change_set_name, - TemplateBody=load_template_raw(template_path), - ChangeSetType="CREATE", - ) - - -@pytest.mark.skip("CFNV2:Resolve") -@markers.aws.validated -def test_create_change_set_with_ssm_parameter( - cleanup_changesets, - cleanup_stacks, - is_change_set_created_and_available, - is_stack_created, - aws_client, -): - """References a simple stack parameter""" - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - parameter_name = f"ls-param-{short_uid()}" - parameter_value = f"ls-param-value-{short_uid()}" - sns_topic_logical_id = "topic123" - parameter_logical_id = "parameter123" - - aws_client.ssm.put_parameter(Name=parameter_name, Value=parameter_value, Type="String") - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamicparameter_ssm_string.yaml" - ) - template_rendered = render_template( - load_template_raw(template_path), parameter_name=parameter_name - ) - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_rendered, - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - # make sure the change set wasn't executed (which would create a new topic) - list_topics_response = aws_client.sns.list_topics() - matching_topics = [ - t for t in list_topics_response["Topics"] if parameter_value in t["TopicArn"] - ] - assert matching_topics == [] - - # stack is initially in REVIEW_IN_PROGRESS state. only after executing the change_set will it change its status - stack_response = aws_client.cloudformation.describe_stacks(StackName=stack_id) - assert stack_response["Stacks"][0]["StackStatus"] == "REVIEW_IN_PROGRESS" - - # Change set can now either be already created/available or it is pending/unavailable - wait_until(is_change_set_created_and_available(change_set_id)) - describe_response = aws_client.cloudformation.describe_change_set( - ChangeSetName=change_set_id - ) - - assert describe_response["ChangeSetName"] == change_set_name - assert describe_response["ChangeSetId"] == change_set_id - assert describe_response["StackId"] == stack_id - assert describe_response["StackName"] == stack_name - assert describe_response["ExecutionStatus"] == "AVAILABLE" - assert describe_response["Status"] == "CREATE_COMPLETE" - changes = describe_response["Changes"] - assert len(changes) == 1 - assert changes[0]["Type"] == "Resource" - assert changes[0]["ResourceChange"]["Action"] == "Add" - assert changes[0]["ResourceChange"]["ResourceType"] == "AWS::SNS::Topic" - assert changes[0]["ResourceChange"]["LogicalResourceId"] == sns_topic_logical_id - - parameters = describe_response["Parameters"] - assert len(parameters) == 1 - assert parameters[0]["ParameterKey"] == parameter_logical_id - assert parameters[0]["ParameterValue"] == parameter_name - assert parameters[0]["ResolvedValue"] == parameter_value # the important part - - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - wait_until(is_stack_created(stack_id)) - - topics = aws_client.sns.list_topics() - topic_arns = [x["TopicArn"] for x in topics["Topics"]] - assert any((parameter_value in t) for t in topic_arns) - finally: - cleanup_changesets([change_set_id]) - cleanup_stacks([stack_id]) - - -@markers.aws.validated -def test_describe_change_set_nonexisting(snapshot, aws_client): - with pytest.raises(Exception) as ex: - aws_client.cloudformation.describe_change_set( - StackName="somestack", ChangeSetName="DoesNotExist" - ) - snapshot.match("exception", ex.value) - - -@pytest.mark.skipif( - condition=not is_aws_cloud(), - reason="fails because of the properties mutation in the result_handler", -) -@markers.aws.validated -def test_execute_change_set( - is_change_set_finished, - is_change_set_created_and_available, - is_change_set_failed_and_unavailable, - cleanup_changesets, - cleanup_stacks, - aws_client, -): - """check if executing a change set succeeds in creating/modifying the resources in changed""" - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - template_body = load_template_raw(template_path) - - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert change_set_id - assert stack_id - - try: - assert wait_until(is_change_set_created_and_available(change_set_id=change_set_id)) - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - assert wait_until(is_change_set_finished(change_set_id)) - # check if stack resource was created - topics = aws_client.sns.list_topics() - topic_arns = [x["TopicArn"] for x in topics["Topics"]] - assert any(("sns-topic-simple" in t) for t in topic_arns) - - # new change set name - change_set_name = f"change-set-{short_uid()}" - # check if update with identical stack leads to correct behavior - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="UPDATE", - ) - change_set_id = response["Id"] - stack_id = response["StackId"] - assert wait_until(is_change_set_failed_and_unavailable(change_set_id=change_set_id)) - describe_failed_change_set_result = aws_client.cloudformation.describe_change_set( - ChangeSetName=change_set_id - ) - assert describe_failed_change_set_result["ChangeSetName"] == change_set_name - assert ( - describe_failed_change_set_result["StatusReason"] - == "The submitted information didn't contain changes. Submit different information to create a change set." - ) - with pytest.raises(ClientError) as e: - aws_client.cloudformation.execute_change_set(ChangeSetName=change_set_id) - e.match("InvalidChangeSetStatus") - e.match( - rf"ChangeSet \[{change_set_id}\] cannot be executed in its current status of \[FAILED\]" - ) - finally: - cleanup_changesets([change_set_id]) - cleanup_stacks([stack_id]) - - -@markers.aws.validated -def test_delete_change_set_exception(snapshot, aws_client): - """test error cases when trying to delete a change set""" - with pytest.raises(ClientError) as e1: - aws_client.cloudformation.delete_change_set( - StackName="nostack", ChangeSetName="DoesNotExist" - ) - snapshot.match("e1", e1.value.response) - - with pytest.raises(ClientError) as e2: - aws_client.cloudformation.delete_change_set(ChangeSetName="DoesNotExist") - snapshot.match("e2", e2.value.response) - - -@markers.aws.validated -def test_create_delete_create(aws_client, cleanups, deploy_cfn_template): - """test the re-use of a changeset name with a re-used stack name""" - stack_name = f"stack-{short_uid()}" - change_set_name = f"cs-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - with open(template_path) as infile: - template = infile.read() - - # custom cloudformation deploy process since our `deploy_cfn_template` is too smart and uses IDs, unlike the CDK - def deploy(): - client = aws_client.cloudformation - client.create_change_set( - StackName=stack_name, - TemplateBody=template, - ChangeSetName=change_set_name, - ChangeSetType="CREATE", - ) - client.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=change_set_name - ) - - client.execute_change_set(StackName=stack_name, ChangeSetName=change_set_name) - client.get_waiter("stack_create_complete").wait( - StackName=stack_name, - ) - - def delete(suppress_exception: bool = False): - try: - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - except Exception: - if not suppress_exception: - raise - - deploy() - cleanups.append(lambda: delete(suppress_exception=True)) - delete() - deploy() - - -@pytest.mark.skip(reason="CFNV2:Metadata, CFNV2:Other") -@markers.aws.validated -def test_create_and_then_remove_non_supported_resource_change_set(deploy_cfn_template): - # first deploy cfn with a CodeArtifact resource that is not actually supported - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/code_artifact_template.yaml" - ) - template_body = load_template_raw(template_path) - stack = deploy_cfn_template( - template=template_body, - parameters={"CADomainName": f"domainname-{short_uid()}"}, - ) - - # removal of CodeArtifact should not throw exception - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/code_artifact_remove_template.yaml" - ) - template_body = load_template_raw(template_path) - deploy_cfn_template( - is_update=True, - template=template_body, - stack_name=stack.stack_name, - ) - - -@markers.aws.validated -def test_create_and_then_update_refreshes_template_metadata( - aws_client, - cleanup_changesets, - cleanup_stacks, - is_change_set_finished, - is_change_set_created_and_available, -): - stacks_to_cleanup = set() - changesets_to_cleanup = set() - - try: - stack_name = f"stack-{short_uid()}" - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - - template_body = load_template_raw(template_path) - - create_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=f"change-set-{short_uid()}", - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - - stacks_to_cleanup.add(create_response["StackId"]) - changesets_to_cleanup.add(create_response["Id"]) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=create_response["Id"] - ) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=create_response["Id"] - ) - - wait_until(is_change_set_finished(create_response["Id"])) - - # Note the metadata alone won't change if there are no changes to resources - # TODO: find a better way to make a replacement in yaml template - template_body = template_body.replace( - "TopicName: sns-topic-simple", - "TopicName: sns-topic-simple-updated", - ) - - update_response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=f"change-set-{short_uid()}", - TemplateBody=template_body, - ChangeSetType="UPDATE", - ) - - stacks_to_cleanup.add(update_response["StackId"]) - changesets_to_cleanup.add(update_response["Id"]) - - wait_until(is_change_set_created_and_available(update_response["Id"])) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=update_response["Id"] - ) - - wait_until(is_change_set_finished(update_response["Id"])) - - summary = aws_client.cloudformation.get_template_summary(StackName=stack_name) - - assert "TopicName" in summary["Metadata"] - assert "sns-topic-simple-updated" in summary["Metadata"] - finally: - cleanup_stacks(list(stacks_to_cleanup)) - cleanup_changesets(list(changesets_to_cleanup)) - - -# TODO: the intention of this test is not particularly clear. The resource isn't removed, it'll just generate a new bucket with a new default name -# TODO: rework this to a conditional instead of two templates + parameter usage instead of templating -@markers.aws.validated -def test_create_and_then_remove_supported_resource_change_set(deploy_cfn_template, aws_client): - first_bucket_name = f"test-bucket-1-{short_uid()}" - second_bucket_name = f"test-bucket-2-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/for_removal_setup.yaml" - ) - template_body = load_template_raw(template_path) - - stack = deploy_cfn_template( - template=template_body, - template_mapping={ - "first_bucket_name": first_bucket_name, - "second_bucket_name": second_bucket_name, - }, - ) - assert first_bucket_name in stack.outputs["FirstBucket"] - assert second_bucket_name in stack.outputs["SecondBucket"] - - available_buckets = aws_client.s3.list_buckets() - bucket_names = [bucket["Name"] for bucket in available_buckets["Buckets"]] - assert first_bucket_name in bucket_names - assert second_bucket_name in bucket_names - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/for_removal_remove.yaml" - ) - template_body = load_template_raw(template_path) - stack_updated = deploy_cfn_template( - is_update=True, - template=template_body, - template_mapping={"first_bucket_name": first_bucket_name}, - stack_name=stack.stack_name, - ) - - assert first_bucket_name in stack_updated.outputs["FirstBucket"] - - def assert_bucket_gone(): - available_buckets = aws_client.s3.list_buckets() - bucket_names = [bucket["Name"] for bucket in available_buckets["Buckets"]] - return first_bucket_name in bucket_names and second_bucket_name not in bucket_names - - poll_condition(condition=assert_bucket_gone, timeout=20, interval=5) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..NotificationARNs", - "$..IncludeNestedStacks", - "$..Parameters", - # For V2 - "$..Changes..ResourceChange.Details", - "$..Changes..ResourceChange.Scope", - ] -) -@markers.aws.validated -def test_empty_changeset(snapshot, cleanups, aws_client): - """ - Creates a change set that doesn't actually update any resources and then tries to execute it - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - change_set_name_nochange = f"change-set-nochange-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cdkmetadata.yaml" - ) - template = load_template_file(template_path) - - # 1. create change set and execute - - first_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - ChangeSetType="CREATE", - ) - snapshot.match("first_changeset", first_changeset) - - def _check_changeset_available(): - status = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - )["Status"] - if status == "FAILED": - raise ShortCircuitWaitException("Change set in unrecoverable status") - return status == "CREATE_COMPLETE" - - assert wait_until(_check_changeset_available) - - describe_first_cs = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - ) - snapshot.match("describe_first_cs", describe_first_cs) - assert describe_first_cs["ExecutionStatus"] == "AVAILABLE" - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - ) - - def _check_changeset_success(): - status = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=first_changeset["Id"] - )["ExecutionStatus"] - if status in ["EXECUTE_FAILED", "UNAVAILABLE", "OBSOLETE"]: - raise ShortCircuitWaitException("Change set in unrecoverable status") - return status == "EXECUTE_COMPLETE" - - assert wait_until(_check_changeset_success) - - # 2. create a new change set without changes - nochange_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name_nochange, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - ChangeSetType="UPDATE", - ) - snapshot.match("nochange_changeset", nochange_changeset) - - describe_nochange = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=nochange_changeset["Id"] - ) - snapshot.match("describe_nochange", describe_nochange) - assert describe_nochange["ExecutionStatus"] == "UNAVAILABLE" - - # 3. try to execute the unavailable change set - with pytest.raises(aws_client.cloudformation.exceptions.InvalidChangeSetStatusException) as e: - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=nochange_changeset["Id"] - ) - snapshot.match("error_execute_failed", e.value) - - -@pytest.mark.skip(reason="CFNV2:DeleteChangeSet") -@markers.aws.validated -def test_deleted_changeset(snapshot, cleanups, aws_client): - """simple case verifying that proper exception is thrown when trying to get a deleted changeset""" - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - changeset_name = f"changeset-{short_uid()}" - stack_name = f"stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - snapshot.add_transformer(snapshot.transform.regex(stack_name, "")) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cdkmetadata.yaml" - ) - template = load_template_file(template_path) - - # 1. create change set - create = aws_client.cloudformation.create_change_set( - ChangeSetName=changeset_name, - StackName=stack_name, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND", "CAPABILITY_IAM", "CAPABILITY_NAMED_IAM"], - ChangeSetType="CREATE", - ) - snapshot.match("create", create) - - changeset_id = create["Id"] - - def _check_changeset_available(): - status = aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=changeset_id - )["Status"] - if status == "FAILED": - raise ShortCircuitWaitException("Change set in unrecoverable status") - return status == "CREATE_COMPLETE" - - assert wait_until(_check_changeset_available) - - # 2. delete change set - aws_client.cloudformation.delete_change_set(ChangeSetName=changeset_id, StackName=stack_name) - - with pytest.raises(aws_client.cloudformation.exceptions.ChangeSetNotFoundException) as e: - aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=changeset_id - ) - snapshot.match("postdelete_changeset_notfound", e.value) - - -@markers.aws.validated -def test_autoexpand_capability_requirement(cleanups, aws_client): - stack_name = f"test-stack-{short_uid()}" - changeset_name = f"test-changeset-{short_uid()}" - queue_name = f"test-queue-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - template_body = load_template_raw( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_macro_languageextensions.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.InsufficientCapabilitiesException): - # requires the capability - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template_body, - Parameters=[ - {"ParameterKey": "QueueList", "ParameterValue": "faa,fbb,fcc"}, - {"ParameterKey": "QueueNameParam", "ParameterValue": queue_name}, - ], - ) - - # does not require the capability - create_changeset_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "QueueList", "ParameterValue": "faa,fbb,fcc"}, - {"ParameterKey": "QueueNameParam", "ParameterValue": queue_name}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=create_changeset_result["Id"] - ) - - -# FIXME: a CreateStack operation should work with an existing stack if its in REVIEW_IN_PROGRESS -@pytest.mark.skip(reason="not implemented correctly yet") -@markers.aws.validated -def test_create_while_in_review(aws_client, snapshot, cleanups): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"stack-{short_uid()}" - changeset_name = f"changeset-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - stack_id = changeset["StackId"] - changeset_id = changeset["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=changeset_name - ) - - # I would have actually expected this to throw, but it doesn't - create_stack_while_in_review = aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE - ) - snapshot.match("create_stack_while_in_review", create_stack_while_in_review) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # describe change set and stack (change set is now obsolete) - describe_stack = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_stack", describe_stack) - describe_change_set = aws_client.cloudformation.describe_change_set(ChangeSetName=changeset_id) - snapshot.match("describe_change_set", describe_change_set) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Capabilities", - "$..IncludeNestedStacks", - "$..NotificationARNs", - "$..Parameters", - # V2 parity - "$..Changes..ResourceChange.Details", - "$..Changes..ResourceChange.Scope", - ] -) -@markers.aws.validated -def test_multiple_create_changeset(aws_client, snapshot, cleanups): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"repeated-stack-{short_uid()}" - initial_changeset_name = f"initial-changeset-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - snapshot.match( - "initial_changeset", - aws_client.cloudformation.describe_change_set(ChangeSetName=initial_changeset["Id"]), - ) - - # multiple change sets can exist for a given stack - additional_changeset_name = f"additionalchangeset-{short_uid()}" - additional_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=additional_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - snapshot.match("additional_changeset", additional_changeset) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=additional_changeset_name - ) - - -@pytest.mark.skip(reason="CFNV2:DescribeStacks") -@markers.snapshot.skip_snapshot_verify(paths=["$..LastUpdatedTime", "$..StackStatusReason"]) -@markers.aws.validated -def test_create_changeset_with_stack_id(aws_client, snapshot, cleanups): - """ - The test answers the question if the `StackName` parameter in `CreateChangeSet` can also be a full Stack ID (ARN). - This can make sense in two cases: - 1. a `CREATE` change set type while the stack is in `REVIEW_IN_PROGRESS` (otherwise it would fail) => covered by this test - 2. an `UPDATE` change set type when the stack has been deployed before already - - On an initial `CREATE` we can't actually know the stack ID yet since the `CREATE` will first create the stack. - - Error case: using `CREATE` with a stack ID from a stack that is in `DELETE_COMPLETE` state. - => A single stack instance identified by a unique ID can never leave its `DELETE_COMPLETE` state - => `DELETE_COMPLETE` is the only *real* terminal state of a Stack - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"repeated-stack-{short_uid()}" - initial_changeset_name = "initial-changeset" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - # create initial change set - initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - initial_stack_id = initial_changeset["StackId"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - - # new CREATE change set on stack that is in REVIEW_IN_PROGRESS state - additional_create_changeset_name = "additional-create" - additional_create_changeset = aws_client.cloudformation.create_change_set( - StackName=initial_stack_id, - ChangeSetName=additional_create_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=additional_create_changeset["Id"] - ) - - describe_stack = aws_client.cloudformation.describe_stacks(StackName=initial_stack_id) - snapshot.match("describe_stack", describe_stack) - - # delete and try to revive the stack with the same ID (won't work) - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - assert ( - aws_client.cloudformation.describe_stacks(StackName=initial_stack_id)["Stacks"][0][ - "StackStatus" - ] - == "DELETE_COMPLETE" - ) - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=initial_stack_id, - ChangeSetName="revived-stack-changeset", - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - snapshot.match("recreate_deleted_with_id_exception", e.value.response) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - # gotta skip quite a lot unfortunately - # FIXME: tackle this when fixing API parity of CloudFormation - "$..EnableTerminationProtection", - "$..LastUpdatedTime", - "$..Capabilities", - "$..ChangeSetId", - "$..IncludeNestedStacks", - "$..NotificationARNs", - "$..Parameters", - "$..StackId", - "$..StatusReason", - "$..StackStatusReason", - # V2 parity - "$..Changes..ResourceChange.Details", - "$..Changes..ResourceChange.Scope", - ] -) -@markers.aws.validated -def test_name_conflicts(aws_client, snapshot, cleanups): - """ - changeset-based equivalent to tests.aws.services.cloudformation.api.test_stacks.test_name_conflicts - - Tests behavior of creating a stack and changeset with the same names of ones that were previously deleted - - 1. Create ChangeSet - 2. Create another ChangeSet - 3. Execute ChangeSet / Create Stack - 4. Creating a new ChangeSet (CREATE) for this stack should fail since it already exists & is running/active - 5. Delete Stack - 6. Create ChangeSet / re-use ChangeSet and Stack names from 1. - - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack_name = f"repeated-stack-{short_uid()}" - initial_changeset_name = f"initial-changeset-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - initial_stack_id = initial_changeset["StackId"] - initial_changeset_id = initial_changeset["Id"] - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - - # actually create the stack - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=initial_changeset_name - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # creating should now fail (stack is created & active) - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - snapshot.match("create_changeset_existingstack_exc", e.value.response) - - # delete stack - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - # creating for stack name with same name should work again - # re-using the changset name should also not matter :) - second_initial_changeset = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=initial_changeset_name, - ChangeSetType="CREATE", - TemplateBody=MINIMAL_TEMPLATE, - ) - second_initial_stack_id = second_initial_changeset["StackId"] - second_initial_changeset_id = second_initial_changeset["Id"] - assert second_initial_changeset_id != initial_changeset_id - assert initial_stack_id != second_initial_stack_id - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=second_initial_changeset_id - ) - - # only one should be active, and this one is in review state right now - new_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("new_stack_desc", new_stack_desc) - assert len(new_stack_desc["Stacks"]) == 1 - assert new_stack_desc["Stacks"][0]["StackId"] == second_initial_stack_id - - # can still access both by using the ARN (stack id) - # and they should be different from each other - stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=initial_stack_id) - new_stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=second_initial_stack_id) - snapshot.match("stack_id_desc", stack_id_desc) - snapshot.match("new_stack_id_desc", new_stack_id_desc) - - # can still access all change sets by their ID - initial_changeset_id_desc = aws_client.cloudformation.describe_change_set( - ChangeSetName=initial_changeset_id - ) - snapshot.match("initial_changeset_id_desc", initial_changeset_id_desc) - second_initial_changeset_id_desc = aws_client.cloudformation.describe_change_set( - ChangeSetName=second_initial_changeset_id - ) - snapshot.match("second_initial_changeset_id_desc", second_initial_changeset_id_desc) - - -@markers.aws.validated -def test_describe_change_set_with_similarly_named_stacks(deploy_cfn_template, aws_client): - stack_name = f"stack-{short_uid()}" - change_set_name = f"change-set-{short_uid()}" - - # create a changeset - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_keypair.yml" - ) - template_body = load_template_raw(template_path) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - - # delete the stack - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - # create a new changeset with the same name - response = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - ) - - # ensure that the correct changeset is returned when requested by stack name - assert ( - aws_client.cloudformation.describe_change_set( - ChangeSetName=response["Id"], StackName=stack_name - )["ChangeSetId"] - == response["Id"] - ) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.snapshot.json deleted file mode 100644 index 930b1ff1e8b93..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.snapshot.json +++ /dev/null @@ -1,517 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_update_without_parameters": { - "recorded-date": "31-05-2022, 09:32:02", - "recorded-content": { - "create_change_set": { - "Id": "arn::cloudformation::111111111111:changeSet//", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} - } - }, - "describe_change_set": { - "ChangeSetName": "", - "ChangeSetId": "arn::cloudformation::111111111111:changeSet//", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "Status": "CREATE_COMPLETE", - "NotificationARNs": [], - "RollbackConfiguration": {}, - "Capabilities": [], - "Changes": [ - { - "Type": "Resource", - "ResourceChange": { - "Action": "Modify", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceType": "AWS::SNS::Topic", - "Replacement": "True", - "Scope": [ - "Properties" - ], - "Details": [ - { - "Target": { - "Attribute": "Properties", - "Name": "TopicName", - "RequiresRecreation": "Always" - }, - "Evaluation": "Static", - "ChangeSource": "DirectModification" - } - ] - } - } - ], - "IncludeNestedStacks": false, - "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} - } - }, - "list_change_set": { - "Summaries": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "ChangeSetId": "arn::cloudformation::111111111111:changeSet//", - "ChangeSetName": "", - "ExecutionStatus": "AVAILABLE", - "Status": "CREATE_COMPLETE", - "CreationTime": "datetime", - "IncludeNestedStacks": false - } - ], - "ResponseMetadata": { - "HTTPStatusCode": 200, - "HTTPHeaders": {} - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_empty_changeset": { - "recorded-date": "10-08-2022, 10:52:55", - "recorded-content": { - "first_changeset": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackId": "arn::cloudformation::111111111111:stack//" - }, - "describe_first_cs": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "CDKMetadata", - "ResourceType": "AWS::CDK::Metadata", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE" - }, - "nochange_changeset": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackId": "arn::cloudformation::111111111111:stack//" - }, - "describe_nochange": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [], - "CreationTime": "datetime", - "ExecutionStatus": "UNAVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "FAILED", - "StatusReason": "The submitted information didn't contain changes. Submit different information to create a change set." - }, - "error_execute_failed": "An error occurred (InvalidChangeSetStatus) when calling the ExecuteChangeSet operation: ChangeSet [arn::cloudformation::111111111111:changeSet/] cannot be executed in its current status of [FAILED]" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_deleted_changeset": { - "recorded-date": "11-08-2022, 11:11:47", - "recorded-content": { - "create": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackId": "arn::cloudformation::111111111111:stack//" - }, - "postdelete_changeset_notfound": "An error occurred (ChangeSetNotFound) when calling the DescribeChangeSet operation: ChangeSet [arn::cloudformation::111111111111:changeSet/] does not exist" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_nonexisting": { - "recorded-date": "11-03-2025, 19:12:57", - "recorded-content": { - "exception": "An error occurred (ValidationError) when calling the DescribeChangeSet operation: Stack [somestack] does not exist" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_delete_change_set_exception": { - "recorded-date": "12-03-2025, 10:14:25", - "recorded-content": { - "e1": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [nostack] does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "e2": { - "Error": { - "Code": "ValidationError", - "Message": "StackName must be specified if ChangeSetName is not specified as an ARN.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_name_conflicts": { - "recorded-date": "22-11-2023, 10:58:04", - "recorded-content": { - "create_changeset_existingstack_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [] already exists and cannot be created again with the changeSet [].", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "new_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "REVIEW_IN_PROGRESS", - "StackStatusReason": "User Initiated", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "new_stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "REVIEW_IN_PROGRESS", - "StackStatusReason": "User Initiated", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "initial_changeset_id_desc": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "EXECUTE_COMPLETE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "second_initial_changeset_id_desc": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_while_in_review": { - "recorded-date": "22-11-2023, 08:49:15", - "recorded-content": { - "create_stack_while_in_review": { - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_change_set": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "OBSOLETE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_template_rendering_with_list": { - "recorded-date": "23-11-2023, 09:23:26", - "recorded-content": { - "resolved-template": { - "d": [ - { - "userid": 1 - }, - 1, - "string" - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_changeset_with_stack_id": { - "recorded-date": "28-11-2023, 07:48:23", - "recorded-content": { - "describe_stack": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "REVIEW_IN_PROGRESS", - "StackStatusReason": "User Initiated", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "recreate_deleted_with_id_exception": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [arn::cloudformation::111111111111:stack//] already exists and cannot be created again with the changeSet [revived-stack-changeset].", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_multiple_create_changeset": { - "recorded-date": "28-11-2023, 07:38:49", - "recorded-content": { - "initial_changeset": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "SimpleParam", - "ResourceType": "AWS::SSM::Parameter", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "additional_changeset": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_deleting_resource": { - "recorded-date": "02-06-2025, 10:29:41", - "recorded-content": { - "get-parameter-error": { - "Error": { - "Code": "ParameterNotFound", - "Message": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.validation.json deleted file mode 100644 index fe83ba323389a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.validation.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { - "last_validated_date": "2025-04-03T07:11:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { - "last_validated_date": "2025-04-03T07:13:00+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property]": { - "last_validated_date": "2025-04-03T07:12:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_unrelated_property_not_create_only]": { - "last_validated_date": "2025-04-03T07:12:37+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { - "last_validated_date": "2025-04-03T07:23:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_conditions": { - "last_validated_date": "2025-04-01T14:34:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_direct_update": { - "last_validated_date": "2025-04-01T08:32:30+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_dynamic_update": { - "last_validated_date": "2025-04-01T12:30:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_execute_with_ref": { - "last_validated_date": "2025-04-11T14:34:09+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { - "last_validated_date": "2025-04-01T13:31:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { - "last_validated_date": "2025-04-01T13:20:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_parameter_changes": { - "last_validated_date": "2025-04-01T12:43:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_unrelated_changes_requires_replacement": { - "last_validated_date": "2025-04-01T16:46:22+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestCaptureUpdateProcess::test_unrelated_changes_update_propagation": { - "last_validated_date": "2025-04-01T16:40:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_deleting_resource": { - "last_validated_date": "2025-06-02T10:29:46+00:00", - "durations_in_seconds": { - "setup": 1.06, - "call": 20.61, - "teardown": 4.46, - "total": 26.13 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::TestUpdates::test_simple_update_two_resources": { - "last_validated_date": "2025-04-02T10:05:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_change_set_update_without_parameters": { - "last_validated_date": "2022-05-31T07:32:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_changeset_with_stack_id": { - "last_validated_date": "2023-11-28T06:48:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_delete_create": { - "last_validated_date": "2024-08-13T10:46:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_create_while_in_review": { - "last_validated_date": "2023-11-22T07:49:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_delete_change_set_exception": { - "last_validated_date": "2025-03-12T10:14:25+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_deleted_changeset": { - "last_validated_date": "2022-08-11T09:11:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_nonexisting": { - "last_validated_date": "2025-03-11T19:12:57+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_describe_change_set_with_similarly_named_stacks": { - "last_validated_date": "2024-03-06T13:56:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_empty_changeset": { - "last_validated_date": "2022-08-10T08:52:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_multiple_create_changeset": { - "last_validated_date": "2023-11-28T06:38:49+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_changesets.py::test_name_conflicts": { - "last_validated_date": "2023-11-22T09:58:04+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py deleted file mode 100644 index 483b46808e6a7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py +++ /dev/null @@ -1,36 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.skip(reason="Not implemented") -@markers.aws.validated -def test_drift_detection_on_lambda(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_simple.yml" - ) - ) - - aws_client.lambda_.update_function_configuration( - FunctionName=stack.outputs["LambdaName"], - Runtime="python3.8", - Description="different description", - Environment={"Variables": {"ENDPOINT_URL": "localhost.localstack.cloud"}}, - ) - - drift_detection = aws_client.cloudformation.detect_stack_resource_drift( - StackName=stack.stack_name, LogicalResourceId="Function" - ) - - snapshot.match("drift_detection", drift_detection) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.snapshot.json deleted file mode 100644 index 8584f783fa4ff..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.snapshot.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py::test_drift_detection_on_lambda": { - "recorded-date": "11-11-2022, 08:44:20", - "recorded-content": { - "drift_detection": { - "StackResourceDrift": { - "ActualProperties": { - "Description": "different description", - "Environment": { - "Variables": { - "ENDPOINT_URL": "localhost.localstack.cloud" - } - }, - "Handler": "index.handler", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.8" - }, - "ExpectedProperties": { - "Description": "function to test lambda function url", - "Environment": { - "Variables": { - "ENDPOINT_URL": "aws.amazon.com" - } - }, - "Handler": "index.handler", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9" - }, - "LogicalResourceId": "Function", - "PhysicalResourceId": "stack-0d03b713-Function-ijoJmdBJP4re", - "PropertyDifferences": [ - { - "ActualValue": "different description", - "DifferenceType": "NOT_EQUAL", - "ExpectedValue": "function to test lambda function url", - "PropertyPath": "/Description" - }, - { - "ActualValue": "localhost.localstack.cloud", - "DifferenceType": "NOT_EQUAL", - "ExpectedValue": "aws.amazon.com", - "PropertyPath": "/Environment/Variables/ENDPOINT_URL" - }, - { - "ActualValue": "python3.8", - "DifferenceType": "NOT_EQUAL", - "ExpectedValue": "python3.9", - "PropertyPath": "/Runtime" - } - ], - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack/stack-0d03b713/", - "StackResourceDriftStatus": "MODIFIED", - "Timestamp": "timestamp" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.validation.json deleted file mode 100644 index 65b14bd8a839d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_drift_detection.py::test_drift_detection_on_lambda": { - "last_validated_date": "2022-11-11T07:44:20+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py deleted file mode 100644 index 8e5e475341e9a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py +++ /dev/null @@ -1,251 +0,0 @@ -import json -import os -import re - -import botocore -import botocore.errorfactory -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsApi: - @pytest.mark.skip(reason="feature not implemented") - @pytest.mark.parametrize( - "extension_type, extension_name, artifact", - [ - ( - "RESOURCE", - "LocalStack::Testing::TestResource", - "resourcetypes/localstack-testing-testresource.zip", - ), - ( - "MODULE", - "LocalStack::Testing::TestModule::MODULE", - "modules/localstack-testing-testmodule-module.zip", - ), - ("HOOK", "LocalStack::Testing::TestHook", "hooks/localstack-testing-testhook.zip"), - ], - ) - @markers.aws.validated - def test_crud_extension( - self, - deploy_cfn_template, - s3_bucket, - snapshot, - extension_name, - extension_type, - artifact, - aws_client, - ): - bucket_name = s3_bucket - artifact_path = os.path.join( - os.path.dirname(__file__), "../artifacts/extensions/", artifact - ) - key_name = f"key-{short_uid()}" - aws_client.s3.upload_file(artifact_path, bucket_name, key_name) - - register_response = aws_client.cloudformation.register_type( - Type=extension_type, - TypeName=extension_name, - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - - snapshot.add_transformer( - snapshot.transform.key_value("RegistrationToken", "registration-token") - ) - snapshot.add_transformer( - snapshot.transform.key_value("DefaultVersionId", "default-version-id") - ) - snapshot.add_transformer(snapshot.transform.key_value("LogRoleArn", "log-role-arn")) - snapshot.add_transformer(snapshot.transform.key_value("LogGroupName", "log-group-name")) - snapshot.add_transformer( - snapshot.transform.key_value("ExecutionRoleArn", "execution-role-arn") - ) - snapshot.match("register_response", register_response) - - describe_type_response = aws_client.cloudformation.describe_type_registration( - RegistrationToken=register_response["RegistrationToken"] - ) - snapshot.match("describe_type_response", describe_type_response) - - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - - describe_response = aws_client.cloudformation.describe_type( - Arn=describe_type_response["TypeArn"], - ) - snapshot.match("describe_response", describe_response) - - list_response = aws_client.cloudformation.list_type_registrations( - TypeName=extension_name, - ) - snapshot.match("list_response", list_response) - - deregister_response = aws_client.cloudformation.deregister_type( - Arn=describe_type_response["TypeArn"] - ) - snapshot.match("deregister_response", deregister_response) - - @pytest.mark.skip(reason="test not completed") - @markers.aws.validated - def test_extension_versioning(self, s3_bucket, snapshot, aws_client): - """ - This tests validates some api behaviours and errors resulting of creating and deleting versions of extensions. - The process of this test: - - register twice the same extension to have multiple versions - - set the last one as a default one. - - try to delete the whole extension. - - try to delete a version of the extension that doesn't exist. - - delete the first version of the extension. - - try to delete the last available version using the version arn. - - delete the whole extension. - """ - bucket_name = s3_bucket - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/modules/localstack-testing-testmodule-module.zip", - ) - key_name = f"key-{short_uid()}" - aws_client.s3.upload_file(artifact_path, bucket_name, key_name) - - register_response = aws_client.cloudformation.register_type( - Type="MODULE", - TypeName="LocalStack::Testing::TestModule::MODULE", - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - - register_response = aws_client.cloudformation.register_type( - Type="MODULE", - TypeName="LocalStack::Testing::TestModule::MODULE", - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - - versions_response = aws_client.cloudformation.list_type_versions( - TypeName="LocalStack::Testing::TestModule::MODULE", Type="MODULE" - ) - snapshot.match("versions", versions_response) - - set_default_response = aws_client.cloudformation.set_type_default_version( - Arn=versions_response["TypeVersionSummaries"][1]["Arn"] - ) - snapshot.match("set_default_response", set_default_response) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.deregister_type( - Type="MODULE", TypeName="LocalStack::Testing::TestModule::MODULE" - ) - snapshot.match("multiple_versions_error", e.value.response) - - arn = versions_response["TypeVersionSummaries"][1]["Arn"] - with pytest.raises(botocore.errorfactory.ClientError) as e: - arn = re.sub(r"/\d{8}", "99999999", arn) - aws_client.cloudformation.deregister_type(Arn=arn) - snapshot.match("version_not_found_error", e.value.response) - - delete_first_version_response = aws_client.cloudformation.deregister_type( - Arn=versions_response["TypeVersionSummaries"][0]["Arn"] - ) - snapshot.match("delete_unused_version_response", delete_first_version_response) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.deregister_type( - Arn=versions_response["TypeVersionSummaries"][1]["Arn"] - ) - snapshot.match("error_for_deleting_default_with_arn", e.value.response) - - delete_default_response = aws_client.cloudformation.deregister_type( - Type="MODULE", TypeName="LocalStack::Testing::TestModule::MODULE" - ) - snapshot.match("deleting_default_response", delete_default_response) - - @pytest.mark.skip(reason="feature not implemented") - @markers.aws.validated - def test_extension_not_complete(self, s3_bucket, snapshot, aws_client): - """ - This tests validates the error of Extension not found using the describe_type operation when the registration - of the extension is still in progress. - """ - bucket_name = s3_bucket - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/hooks/localstack-testing-testhook.zip", - ) - key_name = f"key-{short_uid()}" - aws_client.s3.upload_file(artifact_path, bucket_name, key_name) - - register_response = aws_client.cloudformation.register_type( - Type="HOOK", - TypeName="LocalStack::Testing::TestHook", - SchemaHandlerPackage=f"s3://{bucket_name}/{key_name}", - ) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.describe_type( - Type="HOOK", TypeName="LocalStack::Testing::TestHook" - ) - snapshot.match("not_found_error", e.value) - - aws_client.cloudformation.get_waiter("type_registration_complete").wait( - RegistrationToken=register_response["RegistrationToken"] - ) - aws_client.cloudformation.deregister_type( - Type="HOOK", - TypeName="LocalStack::Testing::TestHook", - ) - - @pytest.mark.skip(reason="feature not implemented") - @markers.aws.validated - def test_extension_type_configuration(self, register_extension, snapshot, aws_client): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/hooks/localstack-testing-deployablehook.zip", - ) - extension = register_extension( - extension_type="HOOK", - extension_name="LocalStack::Testing::DeployableHook", - artifact_path=artifact_path, - ) - - extension_configuration = json.dumps( - { - "CloudFormationConfiguration": { - "HookConfiguration": {"TargetStacks": "ALL", "FailureMode": "FAIL"} - } - } - ) - response_set_configuration = aws_client.cloudformation.set_type_configuration( - TypeArn=extension["TypeArn"], Configuration=extension_configuration - ) - snapshot.match("set_type_configuration_response", response_set_configuration) - - with pytest.raises(botocore.errorfactory.ClientError) as e: - aws_client.cloudformation.batch_describe_type_configurations( - TypeConfigurationIdentifiers=[{}] - ) - snapshot.match("batch_describe_configurations_errors", e.value) - - describe = aws_client.cloudformation.batch_describe_type_configurations( - TypeConfigurationIdentifiers=[ - { - "TypeArn": extension["TypeArn"], - }, - ] - ) - snapshot.match("batch_describe_configurations", describe) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.snapshot.json deleted file mode 100644 index 9b165272441a9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.snapshot.json +++ /dev/null @@ -1,687 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": { - "recorded-date": "02-03-2023, 16:11:19", - "recorded-content": { - "register_response": { - "RegistrationToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_type_response": { - "ProgressStatus": "IN_PROGRESS", - "TypeArn": "arn::cloudformation::111111111111:type/resource/LocalStack-Testing-TestResource", - "TypeVersionArn": "arn::cloudformation::111111111111:type/resource/LocalStack-Testing-TestResource/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_response": { - "Arn": "arn::cloudformation::111111111111:type/resource/LocalStack-Testing-TestResource/", - "DefaultVersionId": "", - "DeprecatedStatus": "LIVE", - "Description": "An example resource schema demonstrating some basic constructs and validation rules.", - "ExecutionRoleArn": "", - "IsDefaultVersion": true, - "LastUpdated": "datetime", - "ProvisioningType": "FULLY_MUTABLE", - "Schema": { - "typeName": "LocalStack::Testing::TestResource", - "description": "An example resource schema demonstrating some basic constructs and validation rules.", - "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", - "definitions": {}, - "properties": { - "Name": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "Name" - ], - "createOnlyProperties": [ - "/properties/Name" - ], - "primaryIdentifier": [ - "/properties/Name" - ], - "handlers": { - "create": { - "permissions": [] - }, - "read": { - "permissions": [] - }, - "update": { - "permissions": [] - }, - "delete": { - "permissions": [] - }, - "list": { - "permissions": [] - } - } - }, - "SourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git", - "TimeCreated": "datetime", - "Type": "RESOURCE", - "TypeName": "LocalStack::Testing::TestResource", - "Visibility": "PRIVATE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_response": { - "RegistrationTokenList": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deregister_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": { - "recorded-date": "02-03-2023, 16:11:53", - "recorded-content": { - "register_response": { - "RegistrationToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_type_response": { - "ProgressStatus": "IN_PROGRESS", - "TypeArn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE", - "TypeVersionArn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_response": { - "Arn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/", - "DefaultVersionId": "", - "DeprecatedStatus": "LIVE", - "Description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "IsDefaultVersion": true, - "LastUpdated": "datetime", - "Schema": { - "typeName": "LocalStack::Testing::TestModule::MODULE", - "description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "properties": { - "Parameters": { - "type": "object", - "properties": { - "BucketName": { - "type": "object", - "properties": { - "Type": { - "type": "string" - }, - "Description": { - "type": "string" - } - }, - "required": [ - "Type", - "Description" - ], - "description": "Name for the bucket" - } - } - }, - "Resources": { - "properties": { - "S3Bucket": { - "type": "object", - "properties": { - "Type": { - "type": "string", - "const": "AWS::S3::Bucket" - }, - "Properties": { - "type": "object" - } - } - } - }, - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": true - }, - "TimeCreated": "datetime", - "Type": "MODULE", - "TypeName": "LocalStack::Testing::TestModule::MODULE", - "Visibility": "PRIVATE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_response": { - "RegistrationTokenList": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deregister_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": { - "recorded-date": "02-03-2023, 16:12:56", - "recorded-content": { - "register_response": { - "RegistrationToken": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_type_response": { - "ProgressStatus": "IN_PROGRESS", - "TypeArn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-TestHook", - "TypeVersionArn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-TestHook/", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_response": { - "Arn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-TestHook/", - "ConfigurationSchema": { - "$schema": "http://json-schema.org/draft-07/schema#", - "examples": [ - { - "CloudFormationConfiguration": { - "HookConfiguration": { - "TargetStacks": "ALL", - "Properties": {}, - "FailureMode": "FAIL" - } - } - } - ], - "description": "This schema validates the CFN hook type configuration that could be set by customers", - "additionalProperties": false, - "title": "CloudFormation Hook Type Configuration Schema", - "type": "object", - "definitions": { - "InvocationPoint": { - "description": "Invocation points are the point in provisioning workflow where hooks will be executed.", - "type": "string", - "enum": [ - "PRE_PROVISION" - ] - }, - "HookTarget": { - "description": "Hook targets are the destination where hooks will be invoked against.", - "additionalProperties": false, - "type": "object", - "properties": { - "InvocationPoint": { - "$ref": "#/definitions/InvocationPoint" - }, - "Action": { - "$ref": "#/definitions/Action" - }, - "TargetName": { - "$ref": "#/definitions/TargetName" - } - }, - "required": [ - "TargetName", - "Action", - "InvocationPoint" - ] - }, - "StackRole": { - "pattern": "arn:.+:iam::[0-9]{12}:role/.+", - "description": "The Amazon Resource Name (ARN) of the IAM execution role to use to perform stack operations", - "type": "string", - "maxLength": 256 - }, - "Action": { - "description": "Target actions are the type of operation hooks will be executed at.", - "type": "string", - "enum": [ - "CREATE", - "UPDATE", - "DELETE" - ] - }, - "TargetName": { - "minLength": 1, - "pattern": "^(?!.*\\*\\?).*$", - "description": "Type name of hook target. Hook targets are the destination where hooks will be invoked against.", - "type": "string", - "maxLength": 256 - }, - "StackName": { - "pattern": "^[a-zA-Z][-a-zA-Z0-9]*$", - "description": "CloudFormation Stack name", - "type": "string", - "maxLength": 128 - } - }, - "properties": { - "CloudFormationConfiguration": { - "additionalProperties": false, - "properties": { - "HookConfiguration": { - "additionalProperties": false, - "type": "object", - "properties": { - "TargetStacks": { - "default": "NONE", - "description": "Attribute to specify which stacks this hook applies to or should get invoked for", - "type": "string", - "enum": [ - "ALL", - "NONE" - ] - }, - "StackFilters": { - "description": "Filters to allow hooks to target specific stack attributes", - "additionalProperties": false, - "type": "object", - "properties": { - "FilteringCriteria": { - "default": "ALL", - "description": "Attribute to specify the filtering behavior. ANY will make the Hook pass if one filter matches. ALL will make the Hook pass if all filters match", - "type": "string", - "enum": [ - "ALL", - "ANY" - ] - }, - "StackNames": { - "description": "List of stack names as filters", - "additionalProperties": false, - "type": "object", - "properties": { - "Exclude": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack names that the hook is going to be excluded from", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackName" - } - }, - "Include": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack names that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackName" - } - } - }, - "minProperties": 1 - }, - "StackRoles": { - "description": "List of stack roles that are performing the stack operations.", - "additionalProperties": false, - "type": "object", - "properties": { - "Exclude": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack roles that the hook is going to be excluded from", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackRole" - } - }, - "Include": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "description": "List of stack roles that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/StackRole" - } - } - }, - "minProperties": 1 - } - }, - "required": [ - "FilteringCriteria" - ] - }, - "TargetFilters": { - "oneOf": [ - { - "additionalProperties": false, - "type": "object", - "properties": { - "Actions": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of actions that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/Action" - } - }, - "TargetNames": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of type names that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/TargetName" - } - }, - "InvocationPoints": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of invocation points that the hook is going to target", - "insertionOrder": false, - "type": "array", - "items": { - "$ref": "#/definitions/InvocationPoint" - } - } - }, - "minProperties": 1 - }, - { - "additionalProperties": false, - "type": "object", - "properties": { - "Targets": { - "minItems": 1, - "maxItems": 50, - "uniqueItems": true, - "additionalItems": false, - "description": "List of hook targets", - "type": "array", - "items": { - "$ref": "#/definitions/HookTarget" - } - } - }, - "required": [ - "Targets" - ] - } - ], - "description": "Attribute to specify which targets should invoke the hook", - "type": "object" - }, - "Properties": { - "typeName": "LocalStack::Testing::TestHook", - "description": "Hook runtime properties", - "additionalProperties": false, - "type": "object", - "definitions": {}, - "properties": { - "EncryptionAlgorithm": { - "default": "AES256", - "description": "Encryption algorithm for SSE", - "type": "string" - } - } - }, - "FailureMode": { - "default": "WARN", - "description": "Attribute to specify CloudFormation behavior on hook failure.", - "type": "string", - "enum": [ - "FAIL", - "WARN" - ] - } - }, - "required": [ - "TargetStacks", - "FailureMode" - ] - } - }, - "required": [ - "HookConfiguration" - ] - } - }, - "required": [ - "CloudFormationConfiguration" - ], - "$id": "https://schema.cloudformation..amazonaws.com/cloudformation.hook.configuration.schema.v1.json" - }, - "DefaultVersionId": "", - "DeprecatedStatus": "LIVE", - "Description": "Example resource SSE (Server Side Encryption) verification hook", - "DocumentationUrl": "https://github.com/aws-cloudformation/example-sse-hook/blob/master/README.md", - "IsDefaultVersion": true, - "LastUpdated": "datetime", - "Schema": { - "typeName": "LocalStack::Testing::TestHook", - "description": "Example resource SSE (Server Side Encryption) verification hook", - "sourceUrl": "https://github.com/aws-cloudformation/example-sse-hook", - "documentationUrl": "https://github.com/aws-cloudformation/example-sse-hook/blob/master/README.md", - "typeConfiguration": { - "properties": { - "EncryptionAlgorithm": { - "description": "Encryption algorithm for SSE", - "default": "AES256", - "type": "string" - } - }, - "additionalProperties": false - }, - "required": [], - "handlers": { - "preCreate": { - "targetNames": [ - "AWS::S3::Bucket" - ], - "permissions": [] - }, - "preUpdate": { - "targetNames": [ - "AWS::S3::Bucket" - ], - "permissions": [] - }, - "preDelete": { - "targetNames": [ - "AWS::S3::Bucket" - ], - "permissions": [] - } - }, - "additionalProperties": false - }, - "SourceUrl": "https://github.com/aws-cloudformation/example-sse-hook", - "TimeCreated": "datetime", - "Type": "HOOK", - "TypeName": "LocalStack::Testing::TestHook", - "Visibility": "PRIVATE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_response": { - "RegistrationTokenList": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deregister_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": { - "recorded-date": "02-03-2023, 16:14:12", - "recorded-content": { - "versions": { - "TypeVersionSummaries": [ - { - "Arn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/00000050", - "Description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "IsDefaultVersion": true, - "TimeCreated": "datetime", - "Type": "MODULE", - "TypeName": "LocalStack::Testing::TestModule::MODULE", - "VersionId": "00000050" - }, - { - "Arn": "arn::cloudformation::111111111111:type/module/LocalStack-Testing-TestModule-MODULE/00000051", - "Description": "Schema for Module Fragment of type LocalStack::Testing::TestModule::MODULE", - "IsDefaultVersion": false, - "TimeCreated": "datetime", - "Type": "MODULE", - "TypeName": "LocalStack::Testing::TestModule::MODULE", - "VersionId": "00000051" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "set_default_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "multiple_versions_error": { - "Error": { - "Code": "CFNRegistryException", - "Message": "This type has more than one active version. Please deregister non-default active versions before attempting to deregister the type.", - "Type": "Sender" - }, - "Message": "This type has more than one active version. Please deregister non-default active versions before attempting to deregister the type.", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "version_not_found_error": { - "Error": { - "Code": "CFNRegistryException", - "Message": "TypeName is invalid", - "Type": "Sender" - }, - "Message": "TypeName is invalid", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "delete_unused_version_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "error_for_deleting_default_with_arn": { - "Error": { - "Code": "CFNRegistryException", - "Message": "Version '00000051' is the default version and cannot be deregistered. Deregister the resource type 'LocalStack::Testing::TestModule::MODULE' instead.", - "Type": "Sender" - }, - "Message": "Version '00000051' is the default version and cannot be deregistered. Deregister the resource type 'LocalStack::Testing::TestModule::MODULE' instead.", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleting_default_response": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": { - "recorded-date": "02-03-2023, 16:15:26", - "recorded-content": { - "not_found_error": "An error occurred (TypeNotFoundException) when calling the DescribeType operation: The type 'LocalStack::Testing::TestHook' cannot be found." - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": { - "recorded-date": "06-03-2023, 15:33:33", - "recorded-content": { - "set_type_configuration_response": { - "ConfigurationArn": "arn::cloudformation::111111111111:type-configuration/hook/LocalStack-Testing-DeployableHook/default", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "batch_describe_configurations_errors": "An error occurred (ValidationError) when calling the BatchDescribeTypeConfigurations operation: 1 validation error detected: Value null at 'typeConfigurationIdentifiers' failed to satisfy constraint: Member must not be null", - "batch_describe_configurations": { - "Errors": [], - "TypeConfigurations": [ - { - "Alias": "default", - "Arn": "arn::cloudformation::111111111111:type-configuration/hook/LocalStack-Testing-DeployableHook/default", - "Configuration": { - "CloudFormationConfiguration": { - "HookConfiguration": { - "TargetStacks": "ALL", - "FailureMode": "FAIL" - } - } - }, - "LastUpdated": "datetime", - "TypeArn": "arn::cloudformation::111111111111:type/hook/LocalStack-Testing-DeployableHook" - } - ], - "UnprocessedTypeConfigurations": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.validation.json deleted file mode 100644 index 4687c7c2e5103..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[HOOK-LocalStack::Testing::TestHook-hooks/localstack-testing-testhook.zip]": { - "last_validated_date": "2023-03-02T15:12:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[MODULE-LocalStack::Testing::TestModule::MODULE-modules/localstack-testing-testmodule-module.zip]": { - "last_validated_date": "2023-03-02T15:11:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_crud_extension[RESOURCE-LocalStack::Testing::TestResource-resourcetypes/localstack-testing-testresource.zip]": { - "last_validated_date": "2023-03-02T15:11:19+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_not_complete": { - "last_validated_date": "2023-03-02T15:15:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_type_configuration": { - "last_validated_date": "2023-03-06T14:33:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_api.py::TestExtensionsApi::test_extension_versioning": { - "last_validated_date": "2023-03-02T15:14:12+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py deleted file mode 100644 index 7f3375678845d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py +++ /dev/null @@ -1,81 +0,0 @@ -import json -import os - -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.cloudformation_utils import load_template_file -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsHooks: - @pytest.mark.skip(reason="feature not implemented") - @pytest.mark.parametrize("failure_mode", ["FAIL", "WARN"]) - @markers.aws.validated - def test_hook_deployment( - self, failure_mode, register_extension, snapshot, cleanups, aws_client - ): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/hooks/localstack-testing-deployablehook.zip", - ) - extension = register_extension( - extension_type="HOOK", - extension_name="LocalStack::Testing::DeployableHook", - artifact_path=artifact_path, - ) - - extension_configuration = json.dumps( - { - "CloudFormationConfiguration": { - "HookConfiguration": {"TargetStacks": "ALL", "FailureMode": failure_mode} - } - } - ) - aws_client.cloudformation.set_type_configuration( - TypeArn=extension["TypeArn"], Configuration=extension_configuration - ) - - template = load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/s3_bucket_name.yml", - ) - ) - - stack_name = f"stack-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template, - Parameters=[{"ParameterKey": "Name", "ParameterValue": f"bucket-{short_uid()}"}], - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - if failure_mode == "WARN": - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - else: - with pytest.raises(botocore.exceptions.WaiterError): - aws_client.cloudformation.get_waiter("stack_create_complete").wait( - StackName=stack_name - ) - - events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ - "StackEvents" - ] - - failed_events = [e for e in events if "HookStatusReason" in e] - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer( - snapshot.transform.key_value( - "EventId", value_replacement="", reference_replacement=False - ) - ) - snapshot.match("event_error", failed_events[0]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.snapshot.json deleted file mode 100644 index c75998e8991f9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.snapshot.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": { - "recorded-date": "06-03-2023, 15:00:08", - "recorded-content": { - "event_error": { - "EventId": "", - "HookFailureMode": "FAIL", - "HookInvocationPoint": "PRE_PROVISION", - "HookStatus": "HOOK_COMPLETE_FAILED", - "HookStatusReason": "Hook failed with message: Intentional fail", - "HookType": "LocalStack::Testing::DeployableHook", - "LogicalResourceId": "myb3B4550BC", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": { - "recorded-date": "06-03-2023, 15:01:59", - "recorded-content": { - "event_error": { - "EventId": "", - "HookFailureMode": "WARN", - "HookInvocationPoint": "PRE_PROVISION", - "HookStatus": "HOOK_COMPLETE_FAILED", - "HookStatusReason": "Hook failed with message: Intentional fail. Failure was ignored under WARN mode.", - "HookType": "LocalStack::Testing::DeployableHook", - "LogicalResourceId": "myb3B4550BC", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.validation.json deleted file mode 100644 index f20a821925dd1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[FAIL]": { - "last_validated_date": "2023-03-06T14:00:08+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_hooks.py::TestExtensionsHooks::test_hook_deployment[WARN]": { - "last_validated_date": "2023-03-06T14:01:59+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py deleted file mode 100644 index 73bc059d62288..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py +++ /dev/null @@ -1,47 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsModules: - @pytest.mark.skip(reason="feature not supported") - @markers.aws.validated - def test_module_usage(self, deploy_cfn_template, register_extension, snapshot, aws_client): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/modules/localstack-testing-testmodule-module.zip", - ) - register_extension( - extension_type="MODULE", - extension_name="LocalStack::Testing::TestModule::MODULE", - artifact_path=artifact_path, - ) - - template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/registry/module.yml", - ) - - module_bucket_name = f"bucket-module-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, - parameters={"BucketName": module_bucket_name}, - max_wait=300, - ) - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name)[ - "StackResources" - ] - - snapshot.add_transformer(snapshot.transform.regex(module_bucket_name, "bucket-name-")) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("resource_description", resources[0]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.snapshot.json deleted file mode 100644 index 8696dae584507..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.snapshot.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": { - "recorded-date": "27-02-2023, 16:06:45", - "recorded-content": { - "resource_description": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "BucketModuleS3Bucket", - "ModuleInfo": { - "LogicalIdHierarchy": "BucketModule", - "TypeHierarchy": "LocalStack::Testing::TestModule::MODULE" - }, - "PhysicalResourceId": "bucket-name-hello", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.validation.json deleted file mode 100644 index 8c17cae314b38..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_modules.py::TestExtensionsModules::test_module_usage": { - "last_validated_date": "2023-02-27T15:06:45+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py deleted file mode 100644 index c311980ea441e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestExtensionsResourceTypes: - @pytest.mark.skip(reason="feature not implemented") - @markers.aws.validated - def test_deploy_resource_type( - self, deploy_cfn_template, register_extension, snapshot, aws_client - ): - artifact_path = os.path.join( - os.path.dirname(__file__), - "../artifacts/extensions/resourcetypes/localstack-testing-deployableresource.zip", - ) - - register_extension( - extension_type="RESOURCE", - extension_name="LocalStack::Testing::DeployableResource", - artifact_path=artifact_path, - ) - - template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/registry/resource-provider.yml", - ) - - resource_name = f"name-{short_uid()}" - stack = deploy_cfn_template( - template_path=template_path, parameters={"Name": resource_name}, max_wait=900 - ) - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name)[ - "StackResources" - ] - - snapshot.add_transformer(snapshot.transform.regex(resource_name, "resource-name")) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("resource_description", resources[0]) - - # Make sure to destroy the stack before unregistration - stack.destroy() diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.snapshot.json deleted file mode 100644 index 57898783864f7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.snapshot.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": { - "recorded-date": "28-02-2023, 12:48:27", - "recorded-content": { - "resource_description": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "MyCustomResource", - "PhysicalResourceId": "Test", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "LocalStack::Testing::DeployableResource", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.validation.json deleted file mode 100644 index 51a7ddf2e5932..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_extensions_resourcetypes.py::TestExtensionsResourceTypes::test_deploy_resource_type": { - "last_validated_date": "2023-02-28T11:48:27+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py deleted file mode 100644 index 132669657355d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py +++ /dev/null @@ -1,368 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError, WaiterError - -from localstack import config -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -# pytestmark = pytest.mark.skipif( -# condition=not is_v2_engine() and not is_aws_cloud(), -# reason="Only targeting the new engine", -# ) - -# pytestmark = pytest.mark.skip(reason="CFNV2:NestedStack") - - -@markers.aws.needs_fixing -def test_nested_stack(deploy_cfn_template, s3_create_bucket, aws_client): - # upload template to S3 - artifacts_bucket = f"cf-artifacts-{short_uid()}" - artifacts_path = "stack.yaml" - s3_create_bucket(Bucket=artifacts_bucket, ACL="public-read") - aws_client.s3.put_object( - Bucket=artifacts_bucket, - Key=artifacts_path, - Body=load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/template5.yaml") - ), - ) - - # deploy template - param_value = short_uid() - stack_bucket_name = f"test-{param_value}" # this is the bucket name generated by template5 - - deploy_cfn_template( - template=load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/template6.yaml") - ) - % (artifacts_bucket, artifacts_path), - parameters={"GlobalParam": param_value}, - ) - - # assert that nested resources have been created - def assert_bucket_exists(): - response = aws_client.s3.head_bucket(Bucket=stack_bucket_name) - assert 200 == response["ResponseMetadata"]["HTTPStatusCode"] - - retry(assert_bucket_exists) - - -@markers.aws.validated -def test_nested_stack_output_refs(deploy_cfn_template, s3_create_bucket, aws_client): - """test output handling of nested stacks incl. referencing the nested output in the parent stack""" - bucket_name = s3_create_bucket() - nested_bucket_name = f"test-bucket-nested-{short_uid()}" - key = f"test-key-{short_uid()}" - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-output-refs.nested.yaml", - ), - Bucket=bucket_name, - Key=key, - ) - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-output-refs.yaml" - ), - template_mapping={ - "s3_bucket_url": f"/{bucket_name}/{key}", - "nested_bucket_name": nested_bucket_name, - }, - max_wait=120, # test is flaky, so we need to wait a bit longer - ) - - nested_stack_id = result.outputs["CustomNestedStackId"] - nested_stack_details = aws_client.cloudformation.describe_stacks(StackName=nested_stack_id) - nested_stack_outputs = nested_stack_details["Stacks"][0]["Outputs"] - assert "InnerCustomOutput" not in result.outputs - assert ( - nested_bucket_name - == [ - o["OutputValue"] for o in nested_stack_outputs if o["OutputKey"] == "InnerCustomOutput" - ][0] - ) - assert f"{nested_bucket_name}-suffix" == result.outputs["CustomOutput"] - - -@markers.aws.validated -def test_nested_with_nested_stack(deploy_cfn_template, s3_create_bucket, aws_client): - bucket_name = s3_create_bucket() - bucket_to_create_name = f"test-bucket-{short_uid()}" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - nested_stacks = ["nested_child.yml", "nested_parent.yml"] - urls = [] - - for nested_stack in nested_stacks: - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/", nested_stack), - Bucket=bucket_name, - Key=nested_stack, - ) - - urls.append(f"https://{bucket_name}.s3.{domain}/{nested_stack}") - - outputs = deploy_cfn_template( - max_wait=120 if is_aws_cloud() else None, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested_grand_parent.yml" - ), - parameters={ - "ChildStackURL": urls[0], - "ParentStackURL": urls[1], - "BucketToCreate": bucket_to_create_name, - }, - ).outputs - - assert f"arn:aws:s3:::{bucket_to_create_name}" == outputs["parameterValue"] - - -@markers.aws.validated -@pytest.mark.skip(reason="UPDATE isn't working on nested stacks") -def test_lifecycle_nested_stack(deploy_cfn_template, s3_create_bucket, aws_client): - bucket_name = s3_create_bucket() - nested_bucket_name = f"test-bucket-nested-{short_uid()}" - altered_nested_bucket_name = f"test-bucket-nested-{short_uid()}" - key = f"test-key-{short_uid()}" - - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-output-refs.nested.yaml", - ), - Bucket=bucket_name, - Key=key, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-output-refs.yaml" - ), - template_mapping={ - "s3_bucket_url": f"/{bucket_name}/{key}", - "nested_bucket_name": nested_bucket_name, - }, - ) - assert aws_client.s3.head_bucket(Bucket=nested_bucket_name) - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-output-refs.yaml" - ), - template_mapping={ - "s3_bucket_url": f"/{bucket_name}/{key}", - "nested_bucket_name": altered_nested_bucket_name, - }, - max_wait=120 if is_aws_cloud() else None, - ) - - assert aws_client.s3.head_bucket(Bucket=altered_nested_bucket_name) - - stack.destroy() - - def _assert_bucket_is_deleted(): - try: - aws_client.s3.head_bucket(Bucket=altered_nested_bucket_name) - return False - except ClientError: - return True - - retry(_assert_bucket_is_deleted, retries=5, sleep=2, sleep_before=2) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..Role.AssumeRolePolicyDocument..Action", - "$..Role.Tags", # Moto returns an empty list for no tags - ] -) -@markers.aws.validated -def test_nested_output_in_params(deploy_cfn_template, s3_create_bucket, snapshot, aws_client): - """ - Deploys a Stack with two nested stacks (sub1 and sub2) with a dependency between each other sub2 depends on sub1. - The `sub2` stack uses an output parameter of `sub1` as an input parameter. - - Resources: - - Stack - - 2x Nested Stack - - SNS Topic - - IAM role with policy (sns:Publish) - - """ - # upload template to S3 for nested stacks - template_bucket = f"cfn-root-{short_uid()}" - sub1_path = "sub1.yaml" - sub2_path = "sub2.yaml" - s3_create_bucket(Bucket=template_bucket, ACL="public-read") - aws_client.s3.put_object( - Bucket=template_bucket, - Key=sub1_path, - Body=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-outputref/sub1.yaml", - ) - ), - ) - aws_client.s3.put_object( - Bucket=template_bucket, - Key=sub2_path, - Body=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-outputref/sub2.yaml", - ) - ), - ) - topic_name = f"test-topic-{short_uid()}" - role_name = f"test-role-{short_uid()}" - - if is_aws_cloud(): - base_path = "https://s3.amazonaws.com" - else: - base_path = "http://localhost:4566" - - deploy_cfn_template( - template=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-outputref/root.yaml", - ) - ), - parameters={ - "Sub1TemplateUrl": f"{base_path}/{template_bucket}/{sub1_path}", - "Sub2TemplateUrl": f"{base_path}/{template_bucket}/{sub2_path}", - "TopicName": topic_name, - "RoleName": role_name, - }, - ) - # validations - snapshot.add_transformer(snapshot.transform.key_value("RoleId", "role-id")) - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - snapshot.add_transformer(snapshot.transform.regex(role_name, "")) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - get_role_response = aws_client.iam.get_role(RoleName=role_name) - snapshot.match("get_role_response", get_role_response) - role_policies = aws_client.iam.list_role_policies(RoleName=role_name) - snapshot.match("role_policies", role_policies) - policy_name = role_policies["PolicyNames"][0] - actual_policy = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=policy_name) - snapshot.match("actual_policy", actual_policy) - - sns_pager = aws_client.sns.get_paginator("list_topics") - topics = sns_pager.paginate().build_full_result()["Topics"] - filtered_topics = [t["TopicArn"] for t in topics if topic_name in t["TopicArn"]] - assert len(filtered_topics) == 1 - - -@markers.aws.validated -def test_nested_stacks_conditions(deploy_cfn_template, s3_create_bucket, aws_client): - """ - see: TestCloudFormationConditions.test_condition_on_outputs - - equivalent to the condition test but for a nested stack - """ - bucket_name = s3_create_bucket() - nested_bucket_name = f"test-bucket-nested-{short_uid()}" - key = f"test-key-{short_uid()}" - - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-conditions.nested.yaml", - ), - Bucket=bucket_name, - Key=key, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/nested-stack-conditions.yaml" - ), - parameters={ - "S3BucketPath": f"/{bucket_name}/{key}", - "S3BucketName": nested_bucket_name, - }, - ) - - assert stack.outputs["ProdBucket"] == f"{nested_bucket_name}-prod" - assert aws_client.s3.head_bucket(Bucket=stack.outputs["ProdBucket"]) - - # Ensure that nested stack names are correctly generated - nested_stack = aws_client.cloudformation.describe_stacks( - StackName=stack.outputs["NestedStackArn"] - ) - assert ":" not in nested_stack["Stacks"][0]["StackName"] - - -@pytest.mark.skip("CFNV2:Deletion") -@markers.aws.validated -def test_deletion_of_failed_nested_stack(s3_create_bucket, aws_client, region_name, snapshot): - """ - This test confirms that after deleting a stack parent with a failed nested stack. The nested stack is also deleted - """ - - bucket_name = s3_create_bucket() - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_failed_nested_stack_child.yml" - ), - Bucket=bucket_name, - Key="child.yml", - ) - - stack_name = f"stack-{short_uid()}" - child_template_url = ( - f"https://{bucket_name}.s3.{config.LOCALSTACK_HOST.host_and_port()}/child.yml" - ) - if is_aws_cloud(): - child_template_url = f"https://{bucket_name}.s3.{region_name}.amazonaws.com/child.yml" - - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_failed_nested_stack_parent.yml", - ), - ), - Parameters=[ - {"ParameterKey": "TemplateUri", "ParameterValue": child_template_url}, - ], - OnFailure="DO_NOTHING", - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - - with pytest.raises(WaiterError): - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - stack_status = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0][ - "StackStatus" - ] - assert stack_status == "CREATE_FAILED" - - stacks = aws_client.cloudformation.describe_stacks()["Stacks"] - nested_stack_name = [ - stack for stack in stacks if f"{stack_name}-ChildStack-" in stack["StackName"] - ][0]["StackName"] - - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - with pytest.raises(ClientError) as ex: - aws_client.cloudformation.describe_stacks(StackName=nested_stack_name) - - snapshot.match("error", ex.value.response) - snapshot.add_transformer(snapshot.transform.regex(nested_stack_name, "")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.snapshot.json deleted file mode 100644 index d343aff512da3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.snapshot.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_output_in_params": { - "recorded-date": "07-02-2023, 10:57:47", - "recorded-content": { - "get_role_response": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "IsTruncated": false, - "PolicyNames": [ - "PolicyA" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "actual_policy": { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sns:Publish" - ], - "Effect": "Allow", - "Resource": [ - "arn::sns::111111111111:" - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "PolicyA", - "RoleName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": { - "recorded-date": "17-09-2024, 20:09:36", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Stack with id does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.validation.json deleted file mode 100644 index 26a6749598c8d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_deletion_of_failed_nested_stack": { - "last_validated_date": "2024-09-17T20:09:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_nested_stacks.py::test_nested_output_in_params": { - "last_validated_date": "2023-02-07T09:57:47+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py deleted file mode 100644 index 388826e15e99c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py +++ /dev/null @@ -1,113 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.engine.template_deployer import MOCK_REFERENCE -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.parametrize("attribute_name", ["TopicName", "TopicArn"]) -@markers.aws.validated -def test_nested_getatt_ref(deploy_cfn_template, aws_client, attribute_name, snapshot): - topic_name = f"test-topic-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_getatt_ref.yaml" - ), - parameters={"MyParam": topic_name, "CustomOutputName": attribute_name}, - ) - snapshot.match("outputs", deployment.outputs) - topic_arn = deployment.outputs["MyTopicArn"] - - # Verify the nested GetAtt Ref resolved correctly - custom_ref = deployment.outputs["MyTopicCustom"] - if attribute_name == "TopicName": - assert custom_ref == topic_name - - if attribute_name == "TopicArn": - assert custom_ref == topic_arn - - # Verify resource was created - topic_arns = [t["TopicArn"] for t in aws_client.sns.list_topics()["Topics"]] - assert topic_arn in topic_arns - - -@markers.aws.validated -def test_sub_resolving(deploy_cfn_template, aws_client, snapshot): - """ - Tests different cases for Fn::Sub resolving - - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html - - - TODO: cover all supported functions for VarName / VarValue: - Fn::Base64 - Fn::FindInMap - Fn::GetAtt - Fn::GetAZs - Fn::If - Fn::ImportValue - Fn::Join - Fn::Select - Ref - - """ - topic_name = f"test-topic-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_sub_resovling.yaml" - ), - parameters={"MyParam": topic_name}, - ) - snapshot.match("outputs", deployment.outputs) - topic_arn = deployment.outputs["MyTopicArn"] - - # Verify the parts in the Fn::Sub string are resolved correctly. - sub_output = deployment.outputs["MyTopicSub"] - param, ref, getatt_topicname, getatt_topicarn = sub_output.split("|") - assert param == topic_name - assert ref == topic_arn - assert getatt_topicname == topic_name - assert getatt_topicarn == topic_arn - - map_sub_output = deployment.outputs["MyTopicSubWithMap"] - att_in_map, ref_in_map, static_in_map = map_sub_output.split("|") - assert att_in_map == topic_name - assert ref_in_map == topic_arn - assert static_in_map == "something" - - # Verify resource was created - topic_arns = [t["TopicArn"] for t in aws_client.sns.list_topics()["Topics"]] - assert topic_arn in topic_arns - - -@markers.aws.only_localstack -def test_reference_unsupported_resource(deploy_cfn_template, aws_client): - """ - This test verifies that templates can be deployed even when unsupported resources are references - Make sure to update the template as coverage of resources increases. - """ - # TODO: do we really want to keep this behaviour? - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_ref_unsupported.yml" - ), - ) - - ref_of_unsupported = deployment.outputs["reference"] - value_of_unsupported = deployment.outputs["parameter"] - assert ref_of_unsupported == MOCK_REFERENCE - assert value_of_unsupported == f"The value of the attribute is: {MOCK_REFERENCE}" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.snapshot.json deleted file mode 100644 index 0c364dca777b8..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.snapshot.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": { - "recorded-date": "11-05-2023, 13:43:51", - "recorded-content": { - "outputs": { - "MyTopicArn": "arn::sns::111111111111:", - "MyTopicCustom": "", - "MyTopicName": "", - "MyTopicRef": "arn::sns::111111111111:" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": { - "recorded-date": "11-05-2023, 13:44:18", - "recorded-content": { - "outputs": { - "MyTopicArn": "arn::sns::111111111111:", - "MyTopicCustom": "arn::sns::111111111111:", - "MyTopicName": "", - "MyTopicRef": "arn::sns::111111111111:" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_sub_resolving": { - "recorded-date": "12-05-2023, 07:51:06", - "recorded-content": { - "outputs": { - "MyTopicArn": "arn::sns::111111111111:", - "MyTopicName": "", - "MyTopicRef": "arn::sns::111111111111:", - "MyTopicSub": "|arn::sns::111111111111:||arn::sns::111111111111:", - "MyTopicSubWithMap": "|arn::sns::111111111111:|something" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.validation.json deleted file mode 100644 index eb277de08d538..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicArn]": { - "last_validated_date": "2023-05-11T11:44:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_nested_getatt_ref[TopicName]": { - "last_validated_date": "2023-05-11T11:43:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_reference_resolving.py::test_sub_resolving": { - "last_validated_date": "2023-05-12T05:51:06+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py deleted file mode 100644 index e3cda139c5118..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py +++ /dev/null @@ -1,812 +0,0 @@ -import json -import os - -import botocore.exceptions -import pytest -import yaml - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -def get_events_canceled_by_policy(cfn_client, stack_name): - events = cfn_client.describe_stack_events(StackName=stack_name)["StackEvents"] - - failed_events_by_policy = [ - event - for event in events - if "ResourceStatusReason" in event - and ( - "Action denied by stack policy" in event["ResourceStatusReason"] - or "Action not allowed by stack policy" in event["ResourceStatusReason"] - or "Resource update cancelled" in event["ResourceStatusReason"] - ) - ] - - return failed_events_by_policy - - -def delete_stack_after_process(cfn_client, stack_name): - progress_is_finished = False - while not progress_is_finished: - status = cfn_client.describe_stacks(StackName=stack_name)["Stacks"][0]["StackStatus"] - progress_is_finished = "PROGRESS" not in status - cfn_client.delete_stack(StackName=stack_name) - - -class TestStackPolicy: - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_policy_lifecycle(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("initial_policy", obtained_policy) - - policy = { - "Statement": [ - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"} - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"} - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_updated", obtained_policy) - - policy = {} - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_deleted", obtained_policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_policy_with_url(self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client): - """Test to validate the setting of a Stack Policy through an URL""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - bucket_name = s3_create_bucket() - key = "policy.json" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_policy.json"), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - aws_client.cloudformation.set_stack_policy(StackName=stack.stack_name, StackPolicyURL=url) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_invalid_policy_with_url( - self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client - ): - """Test to validate the error response resulting of setting an invalid Stack Policy through an URL""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - bucket_name = s3_create_bucket() - key = "policy.json" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/invalid_stack_policy.json" - ), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyURL=url - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_empty_policy_with_url( - self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client - ): - """Test to validate the setting of an empty Stack Policy through an URL""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - bucket_name = s3_create_bucket() - key = "policy.json" - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/empty_policy.json"), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - aws_client.cloudformation.set_stack_policy(StackName=stack.stack_name, StackPolicyURL=url) - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_policy_both_policy_and_url( - self, deploy_cfn_template, s3_create_bucket, snapshot, aws_client - ): - """Test to validate the API behavior when trying to set a Stack policy using both the body and the URL""" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - ) - - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - bucket_name = s3_create_bucket() - key = "policy.json" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_policy.json"), - Bucket=bucket_name, - Key=key, - ) - - url = f"https://{bucket_name}.s3.{domain}/{key}" - - policy = { - "Statement": [ - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"} - ] - } - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy), StackPolicyURL=url - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_empty_policy(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ), - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = {} - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", policy) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_not_json_policy(self, deploy_cfn_template, snapshot, aws_client): - """Test to validate the error response when setting and Invalid Policy""" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ), - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=short_uid() - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_different_principal_attribute(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": short_uid(), - "Resource": "*", - } - ] - } - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - error_response = ex.value.response["Error"] - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_different_action_attribute(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Delete:*", - "Principal": short_uid(), - "Resource": "*", - } - ] - } - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - @pytest.mark.parametrize("resource_type", ["AWS::S3::Bucket", "AWS::SNS::Topic"]) - def test_prevent_update(self, resource_type, deploy_cfn_template, aws_client): - """ - Test to validate the correct behavior of the update operation on a Stack with a Policy that prevents an update - for a specific resource type - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*", - "Condition": {"StringEquals": {"ResourceType": [resource_type]}}, - }, - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - {"ParameterKey": "BucketName", "ParameterValue": f"new-bucket-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - # if the policy prevents one resource to update the whole update fails - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=5, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - @pytest.mark.parametrize( - "resource", - [ - {"id": "bucket123", "type": "AWS::S3::Bucket"}, - {"id": "topic123", "type": "AWS::SNS::Topic"}, - ], - ) - def test_prevent_deletion(self, resource, deploy_cfn_template, aws_client): - """ - Test to validate that CFn won't delete resources during an update operation that are protected by the Stack - Policy - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:Delete", - "Principal": "*", - "Resource": "*", - "Condition": {"StringEquals": {"ResourceType": [resource["type"]]}}, - } - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - template_dict = yaml.load(template) - del template_dict["Resources"][resource["id"]] - template = yaml.dump(template_dict) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - {"ParameterKey": "BucketName", "ParameterValue": f"new-bucket-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=6, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_prevent_modifying_with_policy_specifying_resource_id( - self, deploy_cfn_template, aws_client - ): - """ - Test to validate that CFn won't modify a resource protected by a stack policy that specifies the resource - using the logical Resource Id - """ - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/simple_api.yaml") - ) - stack = deploy_cfn_template( - template=template, - parameters={"ApiName": f"api-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:Modify", - "Principal": "*", - "Resource": "LogicalResourceId/Api", - } - ] - } - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - TemplateBody=template, - StackName=stack.stack_name, - Parameters=[ - {"ParameterKey": "ApiName", "ParameterValue": f"new-api-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=6, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_prevent_replacement(self, deploy_cfn_template, aws_client): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:Replace", - "Principal": "*", - "Resource": "*", - } - ] - } - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"bucket-{short_uid()}"}, - ], - ) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - try: - retry(_assert_failing_update_state, retries=6, sleep=2, sleep_before=2) - finally: - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_update_with_policy(self, deploy_cfn_template, aws_client): - """ - Test to validate the completion of a stack update that is allowed by the Stack Policy - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - policy = { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*", - "Condition": {"StringEquals": {"ResourceType": ["AWS::EC2::Subnet"]}}, - }, - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_update_with_empty_policy(self, deploy_cfn_template, is_stack_updated, aws_client): - """ - Test to validate the behavior of a stack update that has an empty Stack Policy - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/stack_policy_test.yaml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}", "BucketName": f"bucket-{short_uid()}"}, - ) - aws_client.cloudformation.set_stack_policy(StackName=stack.stack_name, StackPolicyBody="{}") - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - {"ParameterKey": "BucketName", "ParameterValue": f"new-bucket-{short_uid()}"}, - ], - ) - - def _assert_stack_is_updated(): - assert is_stack_updated(stack.stack_name) - - retry(_assert_stack_is_updated, retries=5, sleep=2, sleep_before=1) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - @pytest.mark.parametrize("reverse_statements", [False, True]) - def test_update_with_overlapping_policies( - self, reverse_statements, deploy_cfn_template, is_stack_updated, aws_client - ): - """ - This test validates the behaviour when two statements in policy contradict each other. - According to the AWS triage, the last statement is the one that is followed. - """ - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - statements = [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - - if reverse_statements: - statements.reverse() - - policy = {"Statement": statements} - - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"}, - ], - ) - - def _assert_stack_is_updated(): - assert is_stack_updated(stack.stack_name) - - def _assert_failing_update_state(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - retry( - _assert_stack_is_updated if not reverse_statements else _assert_failing_update_state, - retries=5, - sleep=2, - sleep_before=2, - ) - - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_create_stack_with_policy(self, snapshot, cleanup_stacks, aws_client): - stack_name = f"stack-{short_uid()}" - - policy = { - "Statement": [ - {"Effect": "Allow", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - - aws_client.cloudformation.create_stack( - StackName=stack_name, - StackPolicyBody=json.dumps(policy), - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"} - ], - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack_name) - snapshot.match("policy", obtained_policy) - cleanup_stacks([stack_name]) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_set_policy_with_update_operation( - self, deploy_cfn_template, is_stack_updated, snapshot, cleanup_stacks, aws_client - ): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/simple_api.yaml") - ) - stack = deploy_cfn_template( - template=template, - parameters={"ApiName": f"api-{short_uid()}"}, - ) - - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "ApiName", "ParameterValue": f"api-{short_uid()}"}, - ], - StackPolicyBody=json.dumps(policy), - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy", obtained_policy) - - # This part makes sure that the policy being set during the last update doesn't affect the requested changes - def _assert_stack_is_updated(): - assert is_stack_updated(stack.stack_name) - - retry(_assert_stack_is_updated, retries=5, sleep=2, sleep_before=1) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_after_update", obtained_policy) - - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="Not implemented") - def test_policy_during_update( - self, deploy_cfn_template, is_stack_updated, snapshot, cleanup_stacks, aws_client - ): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/simple_api.yaml") - ) - stack = deploy_cfn_template( - template=template, - parameters={"ApiName": f"api-{short_uid()}"}, - ) - - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "ApiName", "ParameterValue": f"api-{short_uid()}"}, - ], - StackPolicyDuringUpdateBody=json.dumps(policy), - ) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_during_update", obtained_policy) - - def _assert_update_failed(): - assert get_events_canceled_by_policy(aws_client.cloudformation, stack.stack_name) - - retry(_assert_update_failed, retries=5, sleep=2, sleep_before=1) - - obtained_policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - snapshot.match("policy_after_update", obtained_policy) - - delete_stack_after_process(aws_client.cloudformation, stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="feature not implemented") - def test_prevent_stack_update(self, deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - stack = deploy_cfn_template( - template=template, parameters={"TopicName": f"topic-{short_uid()}"} - ) - policy = { - "Statement": [ - {"Effect": "Deny", "Action": "Update:*", "Principal": "*", "Resource": "*"}, - ] - } - aws_client.cloudformation.set_stack_policy( - StackName=stack.stack_name, StackPolicyBody=json.dumps(policy) - ) - - policy = aws_client.cloudformation.get_stack_policy(StackName=stack.stack_name) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"new-topic-{short_uid()}"} - ], - ) - - def _assert_failing_update_state(): - events = aws_client.cloudformation.describe_stack_events(StackName=stack.stack_name)[ - "StackEvents" - ] - failed_event_update = [ - event for event in events if event["ResourceStatus"] == "UPDATE_FAILED" - ] - assert failed_event_update - assert "Action denied by stack policy" in failed_event_update[0]["ResourceStatusReason"] - - try: - retry(_assert_failing_update_state, retries=5, sleep=2, sleep_before=2) - finally: - progress_is_finished = False - while not progress_is_finished: - status = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)[ - "Stacks" - ][0]["StackStatus"] - progress_is_finished = "PROGRESS" not in status - aws_client.cloudformation.delete_stack(StackName=stack.stack_name) - - @markers.aws.validated - @pytest.mark.skip(reason="feature not implemented") - def test_prevent_resource_deletion(self, deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ) - - template = template.replace("DeletionPolicy: Delete", "DeletionPolicy: Retain") - stack = deploy_cfn_template( - template=template, parameters={"TopicName": f"topic-{short_uid()}"} - ) - aws_client.cloudformation.delete_stack(StackName=stack.stack_name) - - aws_client.sns.get_topic_attributes(TopicArn=stack.outputs["TopicArn"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.snapshot.json deleted file mode 100644 index 46160d7841335..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.snapshot.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": { - "recorded-date": "10-11-2022, 12:40:34", - "recorded-content": { - "policy": { - "StackPolicyBody": {}, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_invalid_policy": { - "recorded-date": "14-11-2022, 15:13:18", - "recorded-content": { - "error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": { - "recorded-date": "15-11-2022, 16:02:20", - "recorded-content": { - "initial_policy": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Allow", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_updated": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_deleted": { - "StackPolicyBody": {}, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": { - "recorded-date": "11-11-2022, 13:58:17", - "recorded-content": { - "policy": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Allow", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": { - "recorded-date": "11-11-2022, 14:07:44", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": { - "recorded-date": "11-11-2022, 14:19:19", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "You cannot specify both StackPolicyURL and StackPolicyBody", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": { - "recorded-date": "11-11-2022, 14:25:18", - "recorded-content": { - "policy": { - "StackPolicyBody": {}, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": { - "recorded-date": "21-11-2022, 15:48:27", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": { - "recorded-date": "16-11-2022, 11:01:36", - "recorded-content": { - "error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": { - "recorded-date": "21-11-2022, 15:44:16", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Error validating stack policy: Invalid stack policy", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": { - "recorded-date": "16-11-2022, 15:42:23", - "recorded-content": { - "policy": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Allow", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": { - "recorded-date": "17-11-2022, 11:04:31", - "recorded-content": { - "policy": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_after_update": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": { - "recorded-date": "17-11-2022, 11:09:28", - "recorded-content": { - "policy_during_update": { - "StackPolicyBody": { - "Statement": [ - { - "Effect": "Deny", - "Action": "Update:*", - "Principal": "*", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "policy_after_update": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": { - "recorded-date": "28-10-2022, 12:10:42", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": { - "recorded-date": "28-10-2022, 12:29:11", - "recorded-content": {} - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.validation.json deleted file mode 100644 index 3b728f9fbb277..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.validation.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_create_stack_with_policy": { - "last_validated_date": "2022-11-16T14:42:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_action_attribute": { - "last_validated_date": "2022-11-21T14:44:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_different_principal_attribute": { - "last_validated_date": "2022-11-16T10:01:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_empty_policy": { - "last_validated_date": "2022-11-10T11:40:34+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_not_json_policy": { - "last_validated_date": "2022-11-21T14:48:27+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_during_update": { - "last_validated_date": "2022-11-17T10:09:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_policy_lifecycle": { - "last_validated_date": "2022-11-15T15:02:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_resource_deletion": { - "last_validated_date": "2022-10-28T10:29:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_prevent_stack_update": { - "last_validated_date": "2022-10-28T10:10:42+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_empty_policy_with_url": { - "last_validated_date": "2022-11-11T13:25:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_invalid_policy_with_url": { - "last_validated_date": "2022-11-11T13:07:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_both_policy_and_url": { - "last_validated_date": "2022-11-11T13:19:19+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_update_operation": { - "last_validated_date": "2022-11-17T10:04:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stack_policies.py::TestStackPolicy::test_set_policy_with_url": { - "last_validated_date": "2022-11-11T12:58:17+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py deleted file mode 100644 index b65f0fae31ebe..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py +++ /dev/null @@ -1,1072 +0,0 @@ -import json -import os -from collections import OrderedDict -from itertools import permutations - -import botocore.exceptions -import pytest -import yaml -from botocore.exceptions import ClientError, WaiterError -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.aws.api.cloudformation import Capability -from localstack.services.cloudformation.engine.entities import StackIdentifier -from localstack.services.cloudformation.engine.yaml_parser import parse_yaml -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry, wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestStacksApi: - @pytest.mark.skip("CFNV2:DescribeStacks") - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..ChangeSetId", - "$..EnableTerminationProtection", - # V2 - "$..Parameters", - ] - ) - @markers.aws.validated - def test_stack_lifecycle(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("ParameterValue", "parameter-value")) - api_name = f"test_{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.yaml" - ) - - deployed = deploy_cfn_template( - template_path=template_path, - parameters={"ApiName": api_name}, - ) - stack_name = deployed.stack_name - creation_description = aws_client.cloudformation.describe_stacks(StackName=stack_name)[ - "Stacks" - ][0] - snapshot.match("creation", creation_description) - - api_name = f"test_{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=deployed.stack_name, - template_path=template_path, - parameters={"ApiName": api_name}, - ) - update_description = aws_client.cloudformation.describe_stacks(StackName=stack_name)[ - "Stacks" - ][0] - snapshot.match("update", update_description) - - aws_client.cloudformation.delete_stack( - StackName=stack_name, - ) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("describe_deleted_by_name_exc", e.value.response) - - deleted = aws_client.cloudformation.describe_stacks(StackName=deployed.stack_id)["Stacks"][ - 0 - ] - assert "DeletionTime" in deleted - snapshot.match("deleted", deleted) - - @pytest.mark.skip(reason="CFNV2:DescribeStacks") - @markers.aws.validated - def test_stack_description_special_chars(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "test .test.net", - "Resources": { - "TestResource": { - "Type": "AWS::EC2::VPC", - "Properties": {"CidrBlock": "100.30.20.0/20"}, - } - }, - } - deployed = deploy_cfn_template(template=json.dumps(template)) - response = aws_client.cloudformation.describe_stacks(StackName=deployed.stack_id)["Stacks"][ - 0 - ] - snapshot.match("describe_stack", response) - - @markers.aws.validated - def test_stack_name_creation(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"*@{short_uid()}_$" - - with pytest.raises(Exception) as e: - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_template.yaml" - ), - stack_name=stack_name, - ) - - snapshot.match("stack_response", e.value.response) - - @markers.aws.validated - @pytest.mark.parametrize("fileformat", ["yaml", "json"]) - def test_get_template_using_create_stack(self, snapshot, fileformat, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"stack-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), - f"../../../../../templates/sns_topic_template.{fileformat}", - ) - ), - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - template_original = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Original" - ) - snapshot.match("template_original", template_original) - - template_processed = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.match("template_processed", template_processed) - - @markers.aws.validated - @pytest.mark.parametrize("fileformat", ["yaml", "json"]) - def test_get_template_using_changesets( - self, deploy_cfn_template, snapshot, fileformat, aws_client - ): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - f"../../../../../templates/sns_topic_template.{fileformat}", - ) - ) - - template_original = aws_client.cloudformation.get_template( - StackName=stack.stack_id, TemplateStage="Original" - ) - snapshot.match("template_original", template_original) - - template_processed = aws_client.cloudformation.get_template( - StackName=stack.stack_id, TemplateStage="Processed" - ) - snapshot.match("template_processed", template_processed) - - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=["$..ParameterValue", "$..PhysicalResourceId", "$..Capabilities"] - ) - def test_stack_update_resources( - self, - deploy_cfn_template, - is_change_set_finished, - is_change_set_created_and_available, - snapshot, - aws_client, - ): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) - - api_name = f"test_{short_uid()}" - - # create stack - deployed = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.yaml" - ), - parameters={"ApiName": api_name}, - ) - stack_name = deployed.stack_name - stack_id = deployed.stack_id - - # assert snapshot of created stack - snapshot.match( - "stack_created", - aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][0], - ) - - # update stack, with one additional resource - api_name = f"test_{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=deployed.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.update.yaml" - ), - parameters={"ApiName": api_name}, - ) - - # assert snapshot of updated stack - snapshot.match( - "stack_updated", - aws_client.cloudformation.describe_stacks(StackName=stack_id)["Stacks"][0], - ) - - # describe stack resources - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name) - snapshot.match("stack_resources", resources) - - @markers.aws.validated - def test_update_stack_with_same_template_withoutchange( - self, deploy_cfn_template, aws_client, snapshot - ): - template = load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_no_change.yaml" - ) - ) - stack = deploy_cfn_template(template=template) - - with pytest.raises(Exception) as ctx: # TODO: capture proper exception - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, TemplateBody=template - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack.stack_name - ) - - snapshot.match("no_change_exception", ctx.value.response) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.validated - def test_update_stack_with_same_template_withoutchange_transformation( - self, deploy_cfn_template, aws_client - ): - template = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/simple_no_change_with_transformation.yaml", - ) - ) - stack = deploy_cfn_template(template=template) - - # transformations will always work even if there's no change in the template! - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack.stack_name - ) - - @markers.aws.validated - def test_update_stack_actual_update(self, deploy_cfn_template, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sqs_queue_update.yml") - ) - queue_name = f"test-queue-{short_uid()}" - stack = deploy_cfn_template( - template=template, parameters={"QueueName": queue_name}, max_wait=360 - ) - - queue_arn_1 = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrl"], AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - assert queue_arn_1 - - stack2 = deploy_cfn_template( - template=template, - stack_name=stack.stack_name, - parameters={"QueueName": f"{queue_name}-new"}, - is_update=True, - max_wait=360, - ) - - queue_arn_2 = aws_client.sqs.get_queue_attributes( - QueueUrl=stack2.outputs["QueueUrl"], AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - assert queue_arn_2 - - assert queue_arn_1 != queue_arn_2 - - @markers.snapshot.skip_snapshot_verify(paths=["$..StackEvents"]) - @markers.aws.validated - def test_list_events_after_deployment(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(SortingTransformer("StackEvents", lambda x: x["Timestamp"])) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ) - ) - response = aws_client.cloudformation.describe_stack_events(StackName=stack.stack_name) - snapshot.match("events", response) - - @markers.aws.validated - @pytest.mark.skip(reason="disable rollback not supported") - @pytest.mark.parametrize("rollback_disabled, length_expected", [(False, 0), (True, 1)]) - def test_failure_options_for_stack_creation( - self, rollback_disabled, length_expected, aws_client - ): - template_with_error = open( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/multiple_bucket.yaml" - ), - "r", - ).read() - - stack_name = f"stack-{short_uid()}" - bucket_1_name = f"bucket-{short_uid()}" - bucket_2_name = f"bucket!#${short_uid()}" - - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template_with_error, - DisableRollback=rollback_disabled, - Parameters=[ - {"ParameterKey": "BucketName1", "ParameterValue": bucket_1_name}, - {"ParameterKey": "BucketName2", "ParameterValue": bucket_2_name}, - ], - ) - - assert wait_until( - lambda _: stack_process_is_finished(aws_client.cloudformation, stack_name), - wait=10, - strategy="exponential", - ) - - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name)[ - "StackResources" - ] - created_resources = [ - resource for resource in resources if "CREATE_COMPLETE" in resource["ResourceStatus"] - ] - assert len(created_resources) == length_expected - - aws_client.cloudformation.delete_stack(StackName=stack_name) - - @markers.aws.validated - @pytest.mark.skipif(reason="disable rollback not enabled", condition=not is_aws_cloud()) - @pytest.mark.parametrize("rollback_disabled, length_expected", [(False, 2), (True, 1)]) - def test_failure_options_for_stack_update( - self, rollback_disabled, length_expected, aws_client, cleanups - ): - stack_name = f"stack-{short_uid()}" - template = open( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/multiple_bucket_update.yaml" - ), - "r", - ).read() - - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template, - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - def _assert_stack_process_finished(): - return stack_process_is_finished(aws_client.cloudformation, stack_name) - - assert wait_until(_assert_stack_process_finished) - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name)[ - "StackResources" - ] - created_resources = [ - resource for resource in resources if "CREATE_COMPLETE" in resource["ResourceStatus"] - ] - assert len(created_resources) == 2 - - aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template, - DisableRollback=rollback_disabled, - Parameters=[ - {"ParameterKey": "Days", "ParameterValue": "-1"}, - ], - ) - - assert wait_until(_assert_stack_process_finished) - - resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name)[ - "StackResources" - ] - updated_resources = [ - resource - for resource in resources - if resource["ResourceStatus"] in ["CREATE_COMPLETE", "UPDATE_COMPLETE"] - ] - assert len(updated_resources) == length_expected - - @markers.aws.only_localstack - def test_create_stack_with_custom_id( - self, aws_client, cleanups, account_id, region_name, set_resource_custom_id - ): - stack_name = f"stack-{short_uid()}" - custom_id = short_uid() - - set_resource_custom_id( - StackIdentifier(account_id, region_name, stack_name), custom_id=custom_id - ) - template = open( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - "r", - ).read() - - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template, - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - assert stack["StackId"].split("/")[-1] == custom_id - - # We need to wait until the stack is created otherwise we can end up in a scenario - # where we try to delete the stack before creating its resources, failing the test - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - -def stack_process_is_finished(cfn_client, stack_name): - return ( - "PROGRESS" - not in cfn_client.describe_stacks(StackName=stack_name)["Stacks"][0]["StackStatus"] - ) - - -@markers.aws.validated -@pytest.mark.skip(reason="Not Implemented") -def test_linting_error_during_creation(snapshot, aws_client): - stack_name = f"stack-{short_uid()}" - bad_template = {"Resources": "", "Outputs": ""} - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=json.dumps(bad_template) - ) - - error_response = ex.value.response - snapshot.match("error", error_response) - - -@markers.aws.validated -@pytest.mark.skip(reason="feature not implemented") -def test_notifications( - deploy_cfn_template, - sns_create_topic, - is_stack_created, - is_stack_updated, - sqs_create_queue, - sns_create_sqs_subscription, - cleanup_stacks, - aws_client, -): - stack_name = f"stack-{short_uid()}" - topic_arn = sns_create_topic()["TopicArn"] - sqs_url = sqs_create_queue() - sns_create_sqs_subscription(topic_arn, sqs_url) - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - aws_client.cloudformation.create_stack( - StackName=stack_name, - NotificationARNs=[topic_arn], - TemplateBody=template, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - cleanup_stacks([stack_name]) - - assert wait_until(is_stack_created(stack_name)) - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}, - ], - ) - assert wait_until(is_stack_updated(stack_name)) - - messages = {} - - def _assert_messages(): - sqs_messages = aws_client.sqs.receive_message(QueueUrl=sqs_url)["Messages"] - for sqs_message in sqs_messages: - sns_message = json.loads(sqs_message["Body"]) - messages.update({sns_message["MessageId"]: sns_message}) - - # Assert notifications of resources created - assert [message for message in messages.values() if "CREATE_" in message["Message"]] - - # Assert notifications of resources deleted - assert [message for message in messages.values() if "UPDATE_" in message["Message"]] - - # Assert notifications of resources deleted - assert [message for message in messages.values() if "DELETE_" in message["Message"]] - - retry(_assert_messages, retries=10, sleep=2) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - # parameters may be out of order - "$..Stacks..Parameters", - ] -) -def test_updating_an_updated_stack_sets_status(deploy_cfn_template, snapshot, aws_client): - """ - The status of a stack that has been updated twice should be "UPDATE_COMPLETE" - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - # need multiple templates to support updates to the stack - template_1 = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_update_1.yaml") - ) - template_2 = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_update_2.yaml") - ) - template_3 = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/stack_update_3.yaml") - ) - - topic_1_name = f"topic-1-{short_uid()}" - topic_2_name = f"topic-2-{short_uid()}" - topic_3_name = f"topic-3-{short_uid()}" - snapshot.add_transformers_list( - [ - snapshot.transform.regex(topic_1_name, "topic-1"), - snapshot.transform.regex(topic_2_name, "topic-2"), - snapshot.transform.regex(topic_3_name, "topic-3"), - ] - ) - - parameters = { - "Topic1Name": topic_1_name, - "Topic2Name": topic_2_name, - "Topic3Name": topic_3_name, - } - - def wait_for(waiter_type: str) -> None: - aws_client.cloudformation.get_waiter(waiter_type).wait( - StackName=stack.stack_name, - WaiterConfig={ - "Delay": 5, - "MaxAttempts": 5, - }, - ) - - stack = deploy_cfn_template(template=template_1, parameters=parameters) - wait_for("stack_create_complete") - - # update the stack - deploy_cfn_template( - template=template_2, - is_update=True, - stack_name=stack.stack_name, - parameters=parameters, - ) - wait_for("stack_update_complete") - - # update the stack again - deploy_cfn_template( - template=template_3, - is_update=True, - stack_name=stack.stack_name, - parameters=parameters, - ) - wait_for("stack_update_complete") - - res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - snapshot.match("describe-result", res) - - -@markers.aws.validated -def test_update_termination_protection(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("ParameterValue", "parameter-value")) - - # create stack - api_name = f"test_{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/simple_api.yaml" - ) - stack = deploy_cfn_template(template_path=template_path, parameters={"ApiName": api_name}) - - # update termination protection (true) - aws_client.cloudformation.update_termination_protection( - EnableTerminationProtection=True, StackName=stack.stack_name - ) - res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - snapshot.match("describe-stack-1", res) - - # update termination protection (false) - aws_client.cloudformation.update_termination_protection( - EnableTerminationProtection=False, StackName=stack.stack_name - ) - res = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - snapshot.match("describe-stack-2", res) - - -@markers.aws.validated -def test_events_resource_types(deploy_cfn_template, snapshot, aws_client): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cdk_sample_app.yaml" - ) - stack = deploy_cfn_template(template_path=template_path, max_wait=500) - events = aws_client.cloudformation.describe_stack_events(StackName=stack.stack_name)[ - "StackEvents" - ] - - resource_types = list({event["ResourceType"] for event in events}) - resource_types.sort() - snapshot.match("resource_types", resource_types) - - -@markers.aws.validated -def test_list_parameter_type(aws_client, deploy_cfn_template, cleanups): - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_parameter_list_type.yaml" - ), - parameters={ - "ParamsList": "foo,bar", - }, - ) - - assert stack.outputs["ParamValue"] == "foo|bar" - - -@markers.aws.validated -@pytest.mark.skipif(condition=not is_aws_cloud(), reason="rollback not implemented") -def test_blocked_stack_deletion(aws_client, cleanups, snapshot): - """ - uses AWS::IAM::Policy for demonstrating this behavior - - 1. create fails - 2. rollback fails even though create didn't even provision anything - 3. trying to delete the stack afterwards also doesn't work - 4. deleting the stack with retain resources works - """ - cfn = aws_client.cloudformation - stack_name = f"test-stacks-blocked-{short_uid()}" - policy_name = f"test-broken-policy-{short_uid()}" - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.regex(policy_name, "")) - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/iam_policy_invalid.yaml") - ) - waiter_config = {"Delay": 1, "MaxAttempts": 20} - - snapshot.add_transformer(snapshot.transform.key_value("PhysicalResourceId")) - snapshot.add_transformer( - snapshot.transform.key_value("ResourceStatusReason", reference_replacement=False) - ) - - stack = cfn.create_stack( - StackName=stack_name, - TemplateBody=template_body, - Parameters=[{"ParameterKey": "Name", "ParameterValue": policy_name}], - Capabilities=[Capability.CAPABILITY_NAMED_IAM], - ) - stack_id = stack["StackId"] - cleanups.append(lambda: cfn.delete_stack(StackName=stack_id, RetainResources=["BrokenPolicy"])) - with pytest.raises(WaiterError): - cfn.get_waiter("stack_create_complete").wait(StackName=stack_id, WaiterConfig=waiter_config) - stack_post_create = cfn.describe_stacks(StackName=stack_id) - snapshot.match("stack_post_create", stack_post_create) - - cfn.delete_stack(StackName=stack_id) - with pytest.raises(WaiterError): - cfn.get_waiter("stack_delete_complete").wait(StackName=stack_id, WaiterConfig=waiter_config) - stack_post_fail_delete = cfn.describe_stacks(StackName=stack_id) - snapshot.match("stack_post_fail_delete", stack_post_fail_delete) - - cfn.delete_stack(StackName=stack_id, RetainResources=["BrokenPolicy"]) - cfn.get_waiter("stack_delete_complete").wait(StackName=stack_id, WaiterConfig=waiter_config) - stack_post_success_delete = cfn.describe_stacks(StackName=stack_id) - snapshot.match("stack_post_success_delete", stack_post_success_delete) - stack_events = cfn.describe_stack_events(StackName=stack_id) - snapshot.match("stack_events", stack_events) - - -MINIMAL_TEMPLATE = """ -Resources: - SimpleParam: - Type: AWS::SSM::Parameter - Properties: - Value: test - Type: String -""" - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.snapshot.skip_snapshot_verify( - paths=["$..EnableTerminationProtection", "$..LastUpdatedTime"] -) -@markers.aws.validated -def test_name_conflicts(aws_client, snapshot, cleanups): - """ - Tests behavior of creating a stack with the same name of one that was previously deleted - - 1. Create Stack - 2. Delete Stack - 3. Create Stack with same name as in 1. - - Step 3 should be successful because you can re-use StackNames, - but only one stack for a given stack name can be `ACTIVE` at one time. - - We didn't exhaustively test yet what is considered as Active by CloudFormation - For now the assumption is that anything != "DELETE_COMPLETED" is considered "ACTIVE" - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - stack_name = f"repeated-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE - ) - stack_id = stack["StackId"] - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # only one can be active at a time - with pytest.raises(aws_client.cloudformation.exceptions.AlreadyExistsException) as e: - aws_client.cloudformation.create_stack(StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE) - snapshot.match("create_stack_already_exists_exc", e.value.response) - - created_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][ - 0 - ]["StackStatus"] - snapshot.match("created_stack_desc", created_stack_desc) - - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - - # describe with name fails - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("deleted_stack_not_found_exc", e.value.response) - - # describe events with name fails - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stack_events(StackName=stack_name) - snapshot.match("deleted_stack_events_not_found_by_name", e.value.response) - - # describe with stack id (ARN) succeeds - deleted_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("deleted_stack_desc", deleted_stack_desc) - - # creating a new stack with the same name as the previously deleted one should work - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, TemplateBody=MINIMAL_TEMPLATE - ) - # should issue a new unique stack ID/ARN - new_stack_id = stack["StackId"] - assert stack_id != new_stack_id - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - new_stack_desc = aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("new_stack_desc", new_stack_desc) - assert len(new_stack_desc["Stacks"]) == 1 - assert new_stack_desc["Stacks"][0]["StackId"] == new_stack_id - - # can still access both by using the ARN (stack id) - # and they should be different from each other - stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=stack_id) - new_stack_id_desc = aws_client.cloudformation.describe_stacks(StackName=new_stack_id) - snapshot.match("stack_id_desc", stack_id_desc) - snapshot.match("new_stack_id_desc", new_stack_id_desc) - - # check if the describing the stack events return the right stack - stack_events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ - "StackEvents" - ] - assert all(stack_event["StackId"] == new_stack_id for stack_event in stack_events) - # describing events by the old stack id should still yield the old events - stack_events = aws_client.cloudformation.describe_stack_events(StackName=stack_id)[ - "StackEvents" - ] - assert all(stack_event["StackId"] == stack_id for stack_event in stack_events) - - # deleting the stack by name should delete the new, not already deleted stack - aws_client.cloudformation.delete_stack(StackName=stack_name) - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack_name) - # describe with stack id returns stack deleted - deleted_stack_desc = aws_client.cloudformation.describe_stacks(StackName=new_stack_id) - snapshot.match("deleted_second_stack_desc", deleted_stack_desc) - - -@markers.aws.validated -def test_describe_stack_events_errors(aws_client, snapshot): - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stack_events() - snapshot.match("describe_stack_events_no_stack_name", e.value.response) - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.describe_stack_events(StackName="does-not-exist") - snapshot.match("describe_stack_events_stack_not_found", e.value.response) - - -TEMPLATE_ORDER_CASES = list(permutations(["A", "B", "C"])) - - -@pytest.mark.skip(reason="CFNV2:Other stack events") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..StackId", - # TODO - "$..PhysicalResourceId", - # TODO - "$..ResourceProperties", - ] -) -@pytest.mark.parametrize( - "deploy_order", TEMPLATE_ORDER_CASES, ids=["-".join(vals) for vals in TEMPLATE_ORDER_CASES] -) -def test_stack_deploy_order(deploy_cfn_template, aws_client, snapshot, deploy_order: tuple[str]): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("EventId")) - resources = { - "A": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": "root", - }, - }, - "B": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": { - "Ref": "A", - }, - }, - }, - "C": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": { - "Ref": "B", - }, - }, - }, - } - - resources = OrderedDict( - [ - (logical_resource_id, resources[logical_resource_id]) - for logical_resource_id in deploy_order - ] - ) - assert len(resources) == 3 - - stack = deploy_cfn_template( - template=json.dumps( - { - "Resources": resources, - } - ) - ) - - stack.destroy() - - events = aws_client.cloudformation.describe_stack_events( - StackName=stack.stack_id, - )["StackEvents"] - - filtered_events = [] - for event in events: - # only the resources we care about - if event["LogicalResourceId"] not in deploy_order: - continue - - # only _COMPLETE events - if not event["ResourceStatus"].endswith("_COMPLETE"): - continue - - filtered_events.append(event) - - # sort by event time - filtered_events.sort(key=lambda e: e["Timestamp"]) - - snapshot.match("events", filtered_events) - - -@pytest.mark.skip(reason="CFNV2:DescribeStack") -@markers.snapshot.skip_snapshot_verify( - paths=[ - # TODO: this property is present in the response from LocalStack when - # there is an active changeset, however it is not present on AWS - # because the change set has not been executed. - "$..Stacks..ChangeSetId", - # FIXME: tackle this when fixing API parity of CloudFormation - "$..Capabilities", - "$..IncludeNestedStacks", - "$..LastUpdatedTime", - "$..NotificationARNs", - "$..ResourceChange", - "$..StackResourceDetail.Metadata", - ] -) -@markers.aws.validated -def test_no_echo_parameter(snapshot, aws_client, deploy_cfn_template): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(SortingTransformer("Parameters", lambda x: x.get("ParameterKey", ""))) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_no_echo.yml" - ) - template = open(template_path, "r").read() - - deployment = deploy_cfn_template( - template=template, - parameters={"SecretParameter": "SecretValue"}, - ) - stack_id = deployment.stack_id - stack_name = deployment.stack_name - - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_stacks", describe_stacks) - - # Check Resource Metadata. - describe_stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=stack_id - ) - for resource in describe_stack_resources["StackResources"]: - resource_logical_id = resource["LogicalResourceId"] - - # Get detailed information about the resource - describe_stack_resource_details = aws_client.cloudformation.describe_stack_resource( - StackName=stack_name, LogicalResourceId=resource_logical_id - ) - snapshot.match( - f"describe_stack_resource_details_{resource_logical_id}", - describe_stack_resource_details, - ) - - # Update stack via update_stack (and change the value of SecretParameter) - aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue1"}, - ], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack_name) - update_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks", update_stacks) - - # Update stack via create_change_set (and change the value of SecretParameter) - change_set_name = f"UpdateSecretParameterValue-{short_uid()}" - aws_client.cloudformation.create_change_set( - StackName=stack_name, - TemplateBody=template, - ChangeSetName=change_set_name, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue2"}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, - ChangeSetName=change_set_name, - ) - change_sets = aws_client.cloudformation.describe_change_set( - StackName=stack_id, - ChangeSetName=change_set_name, - ) - snapshot.match("describe_updated_change_set", change_sets) - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks_change_set", describe_stacks) - - # Change `NoEcho` of a parameter from true to false and update stack via create_change_set. - change_set_name = f"UpdateSecretParameterNoEchoToFalse-{short_uid()}" - template_dict = parse_yaml(load_file(template_path)) - template_dict["Parameters"]["SecretParameter"]["NoEcho"] = False - template_no_echo_false = yaml.dump(template_dict) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - TemplateBody=template_no_echo_false, - ChangeSetName=change_set_name, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue2"}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, - ChangeSetName=change_set_name, - ) - change_sets = aws_client.cloudformation.describe_change_set( - StackName=stack_id, - ChangeSetName=change_set_name, - ) - snapshot.match("describe_updated_change_set_no_echo_true", change_sets) - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks_no_echo_true", describe_stacks) - - # Change `NoEcho` of a parameter back from false to true and update stack via create_change_set. - change_set_name = f"UpdateSecretParameterNoEchoToTrue-{short_uid()}" - aws_client.cloudformation.create_change_set( - StackName=stack_name, - TemplateBody=template, - ChangeSetName=change_set_name, - Parameters=[ - {"ParameterKey": "SecretParameter", "ParameterValue": "NewSecretValue2"}, - ], - ) - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, - ChangeSetName=change_set_name, - ) - change_sets = aws_client.cloudformation.describe_change_set( - StackName=stack_id, - ChangeSetName=change_set_name, - ) - snapshot.match("describe_updated_change_set_no_echo_false", change_sets) - describe_stacks = aws_client.cloudformation.describe_stacks(StackName=stack_id) - snapshot.match("describe_updated_stacks_no_echo_false", describe_stacks) - - -@markers.aws.validated -def test_stack_resource_not_found(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_simple.yaml" - ), - parameters={"TopicName": f"topic{short_uid()}"}, - ) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.describe_stack_resource( - StackName=stack.stack_name, LogicalResourceId="NonExistentResource" - ) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) - snapshot.match("Error", ex.value.response) - - -@markers.aws.validated -def test_no_parameters_given(aws_client, deploy_cfn_template, snapshot): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ) - with pytest.raises(ClientError) as exc_info: - deploy_cfn_template(template_path=template_path) - snapshot.match("deploy-error", exc_info.value) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.snapshot.json deleted file mode 100644 index 63ac9b9c14b57..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.snapshot.json +++ /dev/null @@ -1,2296 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "recorded-date": "05-08-2022, 13:03:43", - "recorded-content": { - "describe_stack": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "Description": "test .test.net", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_update_resources": { - "recorded-date": "30-08-2022, 00:13:26", - "recorded-content": { - "stack_created": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "test_12395eb4" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - }, - "stack_updated": { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "test_5a3df175" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - }, - "stack_resources": { - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - }, - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Api", - "PhysicalResourceId": "", - "ResourceStatus": "UPDATE_COMPLETE", - "ResourceType": "AWS::ApiGateway::RestApi", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "-bucket-10xf2vf1pqap8", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": { - "recorded-date": "05-10-2022, 13:33:55", - "recorded-content": { - "events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "topic123-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "", - "ResourceProperties": { - "TopicName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "topic123-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceProperties": { - "TopicName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "topic123-CREATE_COMPLETE-date", - "LogicalResourceId": "topic123", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceProperties": { - "TopicName": "" - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": { - "recorded-date": "28-11-2023, 13:24:40", - "recorded-content": { - "creation": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - }, - "update": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - }, - "describe_deleted_by_name_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Stack with id does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleted": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_linting_error_during_creation": { - "recorded-date": "11-11-2022, 08:10:14", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Any Resources member must be an object.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_updating_an_updated_stack_sets_status": { - "recorded-date": "02-12-2022, 11:19:41", - "recorded-content": { - "describe-result": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Topic2Name", - "ParameterValue": "topic-2" - }, - { - "ParameterKey": "Topic1Name", - "ParameterValue": "topic-1" - }, - { - "ParameterKey": "Topic3Name", - "ParameterValue": "topic-3" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_update_termination_protection": { - "recorded-date": "04-01-2023, 16:23:22", - "recorded-content": { - "describe-stack-1": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": true, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe-stack-2": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "ApiName", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_events_resource_types": { - "recorded-date": "15-02-2023, 10:46:53", - "recorded-content": { - "resource_types": [ - "AWS::CloudFormation::Stack", - "AWS::SNS::Subscription", - "AWS::SNS::Topic", - "AWS::SQS::Queue", - "AWS::SQS::QueuePolicy" - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_name_creation": { - "recorded-date": "19-04-2023, 12:44:47", - "recorded-content": { - "stack_response": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value '*@da591fa3_$' at 'stackName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*|arn:[-a-zA-Z0-9:/._+]*", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_blocked_stack_deletion": { - "recorded-date": "06-09-2023, 11:01:18", - "recorded-content": { - "stack_post_create": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Name", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "ROLLBACK_FAILED", - "StackStatusReason": "The following resource(s) failed to delete: [BrokenPolicy]. ", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_post_fail_delete": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Name", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_FAILED", - "StackStatusReason": "The following resource(s) failed to delete: [BrokenPolicy]. ", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_post_success_delete": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "Name", - "ParameterValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_SKIPPED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_SKIPPED", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_FAILED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_FAILED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-DELETE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "DELETE_IN_PROGRESS", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-CREATE_FAILED-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "CREATE_FAILED", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "BrokenPolicy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "BrokenPolicy", - "PhysicalResourceId": "", - "ResourceProperties": { - "PolicyName": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "*", - "Resource": "*", - "Effect": "Allow" - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "resource-status-reason", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_name_conflicts": { - "recorded-date": "26-03-2024, 17:59:43", - "recorded-content": { - "create_stack_already_exists_exc": { - "Error": { - "Code": "AlreadyExistsException", - "Message": "Stack [] already exists", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "created_stack_desc": "CREATE_COMPLETE", - "deleted_stack_not_found_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Stack with id does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleted_stack_events_not_found_by_name": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [] does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "deleted_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "new_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "new_stack_id_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "deleted_second_stack_desc": { - "Stacks": [ - { - "CreationTime": "datetime", - "DeletionTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "NotificationARNs": [], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "DELETE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_describe_stack_events_errors": { - "recorded-date": "26-03-2024, 17:54:41", - "recorded-content": { - "describe_stack_events_no_stack_name": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value null at 'stackName' failed to satisfy constraint: Member must not be null", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "describe_stack_events_stack_not_found": { - "Error": { - "Code": "ValidationError", - "Message": "Stack [does-not-exist] does not exist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": { - "recorded-date": "07-05-2024, 08:34:18", - "recorded-content": { - "no_change_exception": { - "Error": { - "Code": "ValidationError", - "Message": "No updates are to be performed.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-B-C]": { - "recorded-date": "29-05-2024, 11:44:14", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xvqPt7CmcHKX", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-FCaKHvMgdicm", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xvqPt7CmcHKX" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-Xr56esN3SasR", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-FCaKHvMgdicm" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-Xr56esN3SasR", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-FCaKHvMgdicm" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-FCaKHvMgdicm", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xvqPt7CmcHKX" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xvqPt7CmcHKX", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-C-B]": { - "recorded-date": "29-05-2024, 11:44:32", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-4tNP69dd8iSL", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-d81WSIsD2X3i", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-4tNP69dd8iSL" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-kStA2w3izJOh", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-d81WSIsD2X3i" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-kStA2w3izJOh", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-d81WSIsD2X3i" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-d81WSIsD2X3i", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-4tNP69dd8iSL" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-4tNP69dd8iSL", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-A-C]": { - "recorded-date": "29-05-2024, 11:44:51", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-a0yQkOAYKMk5", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-RvqPXWdIGzrt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-a0yQkOAYKMk5" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-iPNi3cV9jXAt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-RvqPXWdIGzrt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-iPNi3cV9jXAt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-RvqPXWdIGzrt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-RvqPXWdIGzrt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-a0yQkOAYKMk5" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-a0yQkOAYKMk5", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-C-A]": { - "recorded-date": "29-05-2024, 11:45:12", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xNtQNbQrdc1T", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-UY120OHcpDMZ", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xNtQNbQrdc1T" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-GOhk98pWaTFw", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-UY120OHcpDMZ" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-GOhk98pWaTFw", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-UY120OHcpDMZ" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-UY120OHcpDMZ", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-xNtQNbQrdc1T" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-xNtQNbQrdc1T", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-A-B]": { - "recorded-date": "29-05-2024, 11:45:31", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-BFvOY1qz1Osv", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-qCiX6NdW4hEt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-BFvOY1qz1Osv" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-ki0TLXKJfPgN", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-qCiX6NdW4hEt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-ki0TLXKJfPgN", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-qCiX6NdW4hEt" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-qCiX6NdW4hEt", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-BFvOY1qz1Osv" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-BFvOY1qz1Osv", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-B-A]": { - "recorded-date": "29-05-2024, 11:45:50", - "recorded-content": { - "events": [ - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-LQadBXOC2eGc", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-p6Hy6dxQCfjl", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-LQadBXOC2eGc" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-YYmzIb8agve7", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-p6Hy6dxQCfjl" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "C", - "PhysicalResourceId": "CFN-C-YYmzIb8agve7", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-B-p6Hy6dxQCfjl" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "B", - "PhysicalResourceId": "CFN-B-p6Hy6dxQCfjl", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "CFN-A-LQadBXOC2eGc" - } - }, - { - "StackId": "arn::cloudformation::111111111111:stack//", - "EventId": "", - "StackName": "", - "LogicalResourceId": "A", - "PhysicalResourceId": "CFN-A-LQadBXOC2eGc", - "ResourceType": "AWS::SSM::Parameter", - "Timestamp": "timestamp", - "ResourceStatus": "DELETE_COMPLETE", - "ResourceProperties": { - "Type": "String", - "Value": "root" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_echo_parameter": { - "recorded-date": "19-12-2024, 11:35:19", - "recorded-content": { - "describe_stacks": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "SecretValue" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_resource_details_LocalBucket": { - "StackResourceDetail": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "LocalBucket", - "Metadata": { - "SensitiveData": "SecretValue" - }, - "PhysicalResourceId": "cfn-noecho-bucket", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_change_set": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Modify", - "Details": [ - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - } - ], - "LogicalResourceId": "LocalBucket", - "PhysicalResourceId": "cfn-noecho-bucket", - "Replacement": "False", - "ResourceType": "AWS::S3::Bucket", - "Scope": [ - "Metadata", - "Properties" - ] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks_change_set": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_change_set_no_echo_true": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Modify", - "Details": [ - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - } - ], - "LogicalResourceId": "LocalBucket", - "PhysicalResourceId": "cfn-noecho-bucket", - "Replacement": "False", - "ResourceType": "AWS::S3::Bucket", - "Scope": [ - "Metadata", - "Properties" - ] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "NewSecretValue2" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks_no_echo_true": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_change_set_no_echo_false": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Modify", - "Details": [ - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "CausingEntity": "SecretParameter", - "ChangeSource": "ParameterReference", - "Evaluation": "Static", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Metadata", - "RequiresRecreation": "Never" - } - }, - { - "ChangeSource": "DirectModification", - "Evaluation": "Dynamic", - "Target": { - "Attribute": "Properties", - "Name": "Tags", - "RequiresRecreation": "Never" - } - } - ], - "LogicalResourceId": "LocalBucket", - "PhysicalResourceId": "cfn-noecho-bucket", - "Replacement": "False", - "ResourceType": "AWS::S3::Bucket", - "Scope": [ - "Metadata", - "Properties" - ] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_updated_stacks_no_echo_false": { - "Stacks": [ - { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Outputs": [ - { - "Description": "Secret value from parameter", - "OutputKey": "SecretValue", - "OutputValue": "NewSecretValue1" - } - ], - "Parameters": [ - { - "ParameterKey": "NormalParameter", - "ParameterValue": "Some default value here" - }, - { - "ParameterKey": "SecretParameter", - "ParameterValue": "****" - }, - { - "ParameterKey": "SecretParameterWithDefault", - "ParameterValue": "****" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "UPDATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": { - "recorded-date": "02-01-2025, 19:08:41", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": { - "recorded-date": "02-01-2025, 19:09:40", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": { - "recorded-date": "02-01-2025, 19:11:14", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Resources:\n topic69831491:\n Type: AWS::SNS::Topic\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - topic69831491\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": { - "recorded-date": "02-01-2025, 19:11:20", - "recorded-content": { - "template_original": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "template_processed": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "topic69831491", - "TopicName" - ] - } - } - }, - "Resources": { - "topic69831491": { - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_resource_not_found": { - "recorded-date": "29-01-2025, 09:08:15", - "recorded-content": { - "Error": { - "Error": { - "Code": "ValidationError", - "Message": "Resource NonExistentResource does not exist for stack ", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_parameters_given": { - "recorded-date": "25-07-2025, 15:34:21", - "recorded-content": { - "deploy-error": "An error occurred (ValidationError) when calling the CreateChangeSet operation: Parameters: [Input] must have values" - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.validation.json deleted file mode 100644 index fc5d362607d93..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.validation.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[False-2]": { - "last_validated_date": "2024-06-25T17:21:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_failure_options_for_stack_update[True-1]": { - "last_validated_date": "2024-06-25T17:22:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template[json]": { - "last_validated_date": "2022-08-11T08:55:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template[yaml]": { - "last_validated_date": "2022-08-11T08:55:10+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[json]": { - "last_validated_date": "2025-01-02T19:09:40+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_changesets[yaml]": { - "last_validated_date": "2025-01-02T19:08:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[json]": { - "last_validated_date": "2025-01-02T19:11:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_get_template_using_create_stack[yaml]": { - "last_validated_date": "2025-01-02T19:11:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_list_events_after_deployment": { - "last_validated_date": "2022-10-05T11:33:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_description_special_chars": { - "last_validated_date": "2022-08-05T11:03:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_lifecycle": { - "last_validated_date": "2023-11-28T12:24:40+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_name_creation": { - "last_validated_date": "2023-04-19T10:44:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_stack_update_resources": { - "last_validated_date": "2022-08-29T22:13:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange": { - "last_validated_date": "2024-05-07T08:35:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::TestStacksApi::test_update_stack_with_same_template_withoutchange_transformation": { - "last_validated_date": "2024-05-07T09:26:39+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_blocked_stack_deletion": { - "last_validated_date": "2023-09-06T09:01:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_describe_stack_events_errors": { - "last_validated_date": "2024-03-26T17:54:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_events_resource_types": { - "last_validated_date": "2023-02-15T09:46:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_linting_error_during_creation": { - "last_validated_date": "2022-11-11T07:10:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_name_conflicts": { - "last_validated_date": "2024-03-26T17:59:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_echo_parameter": { - "last_validated_date": "2024-12-19T11:35:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_no_parameters_given": { - "last_validated_date": "2025-07-25T15:34:21+00:00", - "durations_in_seconds": { - "setup": 1.24, - "call": 0.3, - "teardown": 0.0, - "total": 1.54 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2": { - "last_validated_date": "2024-05-21T09:48:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[A-B-C]": { - "last_validated_date": "2024-05-21T10:00:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[A-C-B]": { - "last_validated_date": "2024-05-21T10:01:07+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[B-A-C]": { - "last_validated_date": "2024-05-21T10:01:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[B-C-A]": { - "last_validated_date": "2024-05-21T10:01:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[C-A-B]": { - "last_validated_date": "2024-05-21T10:02:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[C-B-A]": { - "last_validated_date": "2024-05-21T10:02:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order0]": { - "last_validated_date": "2024-05-21T09:49:59+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order1]": { - "last_validated_date": "2024-05-21T09:50:22+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order2]": { - "last_validated_date": "2024-05-21T09:50:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order3]": { - "last_validated_date": "2024-05-21T09:51:07+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order4]": { - "last_validated_date": "2024-05-21T09:51:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order2[deploy_order5]": { - "last_validated_date": "2024-05-21T09:51:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-B-C]": { - "last_validated_date": "2024-05-29T11:44:14+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[A-C-B]": { - "last_validated_date": "2024-05-29T11:44:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-A-C]": { - "last_validated_date": "2024-05-29T11:44:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[B-C-A]": { - "last_validated_date": "2024-05-29T11:45:12+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-A-B]": { - "last_validated_date": "2024-05-29T11:45:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_deploy_order[C-B-A]": { - "last_validated_date": "2024-05-29T11:45:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_stack_resource_not_found": { - "last_validated_date": "2025-01-29T09:08:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_update_termination_protection": { - "last_validated_date": "2023-01-04T15:23:22+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_stacks.py::test_updating_an_updated_stack_sets_status": { - "last_validated_date": "2022-12-02T10:19:41+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py deleted file mode 100644 index fbfe9d191a009..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py +++ /dev/null @@ -1,124 +0,0 @@ -import contextlib -import os -import textwrap - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import load_file -from localstack.utils.strings import short_uid, to_bytes - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..ResourceIdentifierSummaries..ResourceIdentifiers", "$..Parameters"] -) -def test_get_template_summary(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - - deployment = deploy_cfn_template( - template_path=os.path.join( - # This template has no parameters, and so shows the issue - os.path.dirname(__file__), - "../../../../../templates/sns_topic_simple.yaml", - ) - ) - - res = aws_client.cloudformation.get_template_summary(StackName=deployment.stack_name) - - snapshot.match("template-summary", res) - - -@markers.aws.validated -@pytest.mark.parametrize("url_style", ["s3_url", "http_path", "http_host", "http_invalid"]) -def test_create_stack_from_s3_template_url( - url_style, snapshot, s3_create_bucket, aws_client, cleanups -): - topic_name = f"topic-{short_uid()}" - bucket_name = s3_create_bucket() - snapshot.add_transformer(snapshot.transform.regex(topic_name, "")) - snapshot.add_transformer(snapshot.transform.regex(bucket_name, "")) - - stack_name = f"s-{short_uid()}" - template = textwrap.dedent( - """ - AWSTemplateFormatVersion: '2010-09-09' - Parameters: - TopicName: - Type: String - Resources: - topic123: - Type: AWS::SNS::Topic - Properties: - TopicName: !Ref TopicName - """ - ) - - aws_client.s3.put_object(Bucket=bucket_name, Key="test/template.yml", Body=to_bytes(template)) - - match url_style: - case "s3_url": - template_url = f"s3://{bucket_name}/test/template.yml" - case "http_path": - template_url = f"https://s3.amazonaws.com/{bucket_name}/test/template.yml" - case "http_host": - template_url = f"https://{bucket_name}.s3.amazonaws.com/test/template.yml" - case "http_invalid": - # note: using an invalid (non-existing) URL here, but in fact all non-S3 HTTP URLs are invalid in real AWS - template_url = "https://example.com/dummy.yml" - case _: - raise Exception(f"Unexpected `url_style` parameter: {url_style}") - - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - # deploy stack - error_expected = url_style in ["s3_url", "http_invalid"] - context_manager = pytest.raises(ClientError) if error_expected else contextlib.nullcontext() - with context_manager as ctx: - aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateURL=template_url, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": topic_name}], - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - # assert that either error was raised, or topic has been created - if error_expected: - snapshot.match("create-error", ctx.value.response) - else: - results = list(aws_client.sns.get_paginator("list_topics").paginate()) - matching = [ - t for res in results for t in res["Topics"] if t["TopicArn"].endswith(topic_name) - ] - snapshot.match("matching-topic", matching) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Parameters..DefaultValue"]) -def test_validate_template(aws_client, snapshot): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/valid_template.json") - ) - - resp = aws_client.cloudformation.validate_template(TemplateBody=template) - snapshot.match("validate-template", resp) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Error..Message"]) -def test_validate_invalid_json_template_should_fail(aws_client, snapshot): - invalid_json = '{"this is invalid JSON"="bobbins"}' - - with pytest.raises(ClientError) as ctx: - aws_client.cloudformation.validate_template(TemplateBody=invalid_json) - - snapshot.match("validate-invalid-json", ctx.value.response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.snapshot.json deleted file mode 100644 index 66cd35eaffec3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.snapshot.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_get_template_summary": { - "recorded-date": "24-05-2023, 15:05:00", - "recorded-content": { - "template-summary": { - "Metadata": "{'TopicName': 'sns-topic-simple'}", - "Parameters": [], - "ResourceIdentifierSummaries": [ - { - "LogicalResourceIds": [ - "topic123" - ], - "ResourceType": "AWS::SNS::Topic" - } - ], - "ResourceTypes": [ - "AWS::SNS::Topic" - ], - "Version": "2010-09-09", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": { - "recorded-date": "11-10-2023, 00:03:44", - "recorded-content": { - "create-error": { - "Error": { - "Code": "ValidationError", - "Message": "S3 error: Domain name specified in is not a valid S3 domain", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": { - "recorded-date": "11-10-2023, 00:03:53", - "recorded-content": { - "matching-topic": [ - { - "TopicArn": "arn::sns::111111111111:" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": { - "recorded-date": "11-10-2023, 00:04:02", - "recorded-content": { - "matching-topic": [ - { - "TopicArn": "arn::sns::111111111111:" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": { - "recorded-date": "11-10-2023, 00:04:04", - "recorded-content": { - "create-error": { - "Error": { - "Code": "ValidationError", - "Message": "TemplateURL must be a supported URL.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_template": { - "recorded-date": "18-06-2024, 17:23:30", - "recorded-content": { - "validate-template": { - "Parameters": [ - { - "Description": "The EC2 Key Pair to allow SSH access to the instance", - "NoEcho": false, - "ParameterKey": "KeyExample" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_invalid_json_template_should_fail": { - "recorded-date": "18-06-2024, 17:25:49", - "recorded-content": { - "validate-invalid-json": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: JSON not well-formed. (line 1, column 25)", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.validation.json deleted file mode 100644 index 77965368c70b2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_host]": { - "last_validated_date": "2023-10-10T22:04:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_invalid]": { - "last_validated_date": "2023-10-10T22:04:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[http_path]": { - "last_validated_date": "2023-10-10T22:03:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_create_stack_from_s3_template_url[s3_url]": { - "last_validated_date": "2023-10-10T22:03:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_get_template_summary": { - "last_validated_date": "2023-05-24T13:05:00+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_invalid_json_template_should_fail": { - "last_validated_date": "2024-06-18T17:25:49+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_templates.py::test_validate_template": { - "last_validated_date": "2024-06-18T17:23:30+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py deleted file mode 100644 index ecb2d8a625d83..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py +++ /dev/null @@ -1,164 +0,0 @@ -import textwrap - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid, to_bytes - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..tags"]) -def test_duplicate_resources(deploy_cfn_template, s3_bucket, snapshot, aws_client): - snapshot.add_transformers_list( - [ - *snapshot.transform.apigateway_api(), - snapshot.transform.key_value("aws:cloudformation:stack-id"), - snapshot.transform.key_value("aws:cloudformation:stack-name"), - ] - ) - - # put API spec to S3 - api_spec = """ - swagger: 2.0 - info: - version: "1.2.3" - title: "Test API" - basePath: /base - """ - aws_client.s3.put_object(Bucket=s3_bucket, Key="api.yaml", Body=to_bytes(api_spec)) - - # deploy template - template = """ - Parameters: - ApiName: - Type: String - BucketName: - Type: String - Resources: - RestApi: - Type: AWS::ApiGateway::RestApi - Properties: - Name: !Ref ApiName - Body: - 'Fn::Transform': - Name: 'AWS::Include' - Parameters: - Location: !Sub "s3://${BucketName}/api.yaml" - Outputs: - RestApiId: - Value: !Ref RestApi - """ - - api_name = f"api-{short_uid()}" - result = deploy_cfn_template( - template=template, parameters={"ApiName": api_name, "BucketName": s3_bucket} - ) - - # assert REST API is created properly - api_id = result.outputs.get("RestApiId") - result = aws_client.apigateway.get_rest_api(restApiId=api_id) - assert result - snapshot.match("api-details", result) - - resources = aws_client.apigateway.get_resources(restApiId=api_id) - snapshot.match("api-resources", resources) - - -@pytest.mark.skip( - reason=( - "CFNV2:AWS::Include the transformation is run however the " - "physical resource id for the resource is not available" - ) -) -@markers.aws.validated -def test_transformer_property_level(deploy_cfn_template, s3_bucket, aws_client, snapshot): - api_spec = textwrap.dedent(""" - Value: from_transformation - """) - aws_client.s3.put_object(Bucket=s3_bucket, Key="data.yaml", Body=to_bytes(api_spec)) - - # deploy template - template = textwrap.dedent(""" - Parameters: - BucketName: - Type: String - Resources: - MyParameter: - Type: AWS::SSM::Parameter - Properties: - Description: hello - Type: String - "Fn::Transform": - Name: "AWS::Include" - Parameters: - Location: !Sub "s3://${BucketName}/data.yaml" - Outputs: - ParameterName: - Value: !Ref MyParameter - """) - - result = deploy_cfn_template(template=template, parameters={"BucketName": s3_bucket}) - param_name = result.outputs["ParameterName"] - param = aws_client.ssm.get_parameter(Name=param_name) - assert ( - param["Parameter"]["Value"] == "from_transformation" - ) # value coming from the transformation - describe_result = ( - aws_client.ssm.get_paginator("describe_parameters") - .paginate(Filters=[{"Key": "Name", "Values": [param_name]}]) - .build_full_result() - ) - assert ( - describe_result["Parameters"][0]["Description"] == "hello" - ) # value from a property on the same level as the transformation - - original_template = aws_client.cloudformation.get_template( - StackName=result.stack_id, TemplateStage="Original" - ) - snapshot.match("original_template", original_template) - processed_template = aws_client.cloudformation.get_template( - StackName=result.stack_id, TemplateStage="Processed" - ) - snapshot.match("processed_template", processed_template) - - -@pytest.mark.skip( - reason=( - "CFNV2:AWS::Include the transformation is run however the " - "physical resource id for the resource is not available" - ) -) -@markers.aws.validated -def test_transformer_individual_resource_level(deploy_cfn_template, s3_bucket, aws_client): - api_spec = textwrap.dedent(""" - Type: AWS::SNS::Topic - """) - aws_client.s3.put_object(Bucket=s3_bucket, Key="data.yaml", Body=to_bytes(api_spec)) - - # deploy template - template = textwrap.dedent(""" - Parameters: - BucketName: - Type: String - Resources: - MyResource: - "Fn::Transform": - Name: "AWS::Include" - Parameters: - Location: !Sub "s3://${BucketName}/data.yaml" - Outputs: - ResourceRef: - Value: !Ref MyResource - """) - - result = deploy_cfn_template(template=template, parameters={"BucketName": s3_bucket}) - resource_ref = result.outputs["ResourceRef"] - # just checking that this doens't fail, i.e. the topic exists - aws_client.sns.get_topic_attributes(TopicArn=resource_ref) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.snapshot.json deleted file mode 100644 index cd79d06b34d9e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.snapshot.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_duplicate_resources": { - "recorded-date": "15-07-2025, 19:28:05", - "recorded-content": { - "api-details": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "RestApi", - "aws:cloudformation:stack-id": "", - "aws:cloudformation:stack-name": "" - }, - "version": "1.2.3", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "api-resources": { - "items": [ - { - "id": "", - "path": "/" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_property_level": { - "recorded-date": "06-06-2024, 10:37:03", - "recorded-content": { - "original_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "\nParameters:\n BucketName:\n Type: String\nResources:\n MyParameter:\n Type: AWS::SSM::Parameter\n Properties:\n Description: hello\n Type: String\n \"Fn::Transform\":\n Name: \"AWS::Include\"\n Parameters:\n Location: !Sub \"s3://${BucketName}/data.yaml\"\nOutputs:\n ParameterName:\n Value: !Ref MyParameter\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "ParameterName": { - "Value": { - "Ref": "MyParameter" - } - } - }, - "Parameters": { - "BucketName": { - "Type": "String" - } - }, - "Resources": { - "MyParameter": { - "Properties": { - "Description": "hello", - "Type": "String", - "Value": "from_transformation" - }, - "Type": "AWS::SSM::Parameter" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.validation.json deleted file mode 100644 index ac2a6ccf07d7d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_duplicate_resources": { - "last_validated_date": "2025-07-15T19:28:15+00:00", - "durations_in_seconds": { - "setup": 1.05, - "call": 13.13, - "teardown": 10.12, - "total": 24.3 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_individual_resource_level": { - "last_validated_date": "2024-06-13T06:43:21+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_transformers.py::test_transformer_property_level": { - "last_validated_date": "2024-06-06T10:38:33+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py deleted file mode 100644 index 01377d88ac8ac..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py +++ /dev/null @@ -1,464 +0,0 @@ -import json -import os -import textwrap - -import botocore.errorfactory -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid -from localstack.utils.testutil import upload_file_to_bucket - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_basic_update(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - response = aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ) - ), - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - snapshot.add_transformer(snapshot.transform.key_value("StackId", "stack-id")) - snapshot.match("update_response", response) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -def test_update_using_template_url(deploy_cfn_template, s3_create_bucket, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - file_url = upload_file_to_bucket( - aws_client.s3, - s3_create_bucket(), - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml"), - )["Url"] - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateURL=file_url, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="Not supported") -def test_update_with_previous_template(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.needs_fixing -@pytest.mark.skip(reason="templates are not partially not valid => re-evaluate") -@pytest.mark.parametrize( - "capability", - [ - {"value": "CAPABILITY_IAM", "template": "iam_policy.yml"}, - {"value": "CAPABILITY_NAMED_IAM", "template": "iam_role_policy.yaml"}, - ], -) -# The AUTO_EXPAND option is used for macros -def test_update_with_capabilities(capability, deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/", capability["template"]) - ) - - parameter_key = "RoleName" if capability["value"] == "CAPABILITY_NAMED_IAM" else "Name" - - with pytest.raises(botocore.errorfactory.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[{"ParameterKey": parameter_key, "ParameterValue": f"{short_uid()}"}], - ) - - snapshot.match("error", ex.value.response) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Capabilities=[capability["value"]], - Parameters=[{"ParameterKey": parameter_key, "ParameterValue": f"{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="Not raising the correct error") -def test_update_with_resource_types(deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - # Test with invalid type - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - ResourceTypes=["AWS::EC2:*"], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - snapshot.match("invalid_type_error", ex.value.response) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - ResourceTypes=["AWS::EC2::*"], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - snapshot.match("resource_not_allowed", ex.value.response) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - ResourceTypes=["AWS::SNS::Topic"], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="Update value not being applied") -def test_set_notification_arn_with_update(deploy_cfn_template, sns_create_topic, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - topic_arn = sns_create_topic()["TopicArn"] - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - NotificationARNs=[topic_arn], - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - description = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)["Stacks"][0] - assert topic_arn in description["NotificationARNs"] - - -@markers.aws.validated -@pytest.mark.skip(reason="Update value not being applied") -def test_update_tags(deploy_cfn_template, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - key = f"key-{short_uid()}" - value = f"value-{short_uid()}" - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - Tags=[{"Key": key, "Value": value}], - TemplateBody=template, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - ) - - tags = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)["Stacks"][0][ - "Tags" - ] - assert tags[0]["Key"] == key - assert tags[0]["Value"] == value - - -@markers.aws.validated -@pytest.mark.skip(reason="The correct error is not being raised") -def test_no_template_error(deploy_cfn_template, snapshot, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack(StackName=stack.stack_name) - - snapshot.match("error", ex.value.response) - - -@markers.aws.validated -def test_no_parameters_update(deploy_cfn_template, aws_client): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack(StackName=stack.stack_name, TemplateBody=template) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@pytest.mark.skip(reason="CFNV2:UpdateStack") -@markers.aws.validated -def test_update_with_previous_parameter_value(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml" - ), - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.update.yml" - ) - ), - Parameters=[{"ParameterKey": "TopicName", "UsePreviousValue": True}], - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - -@markers.aws.validated -@pytest.mark.skip(reason="The correct error is not being raised") -def test_update_with_role_without_permissions( - deploy_cfn_template, snapshot, create_role, aws_client -): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - account_arn = aws_client.sts.get_caller_identity()["Arn"] - assume_policy_doc = { - "Version": "2012-10-17", - "Statement": [ - { - "Action": "sts:AssumeRole", - "Principal": {"AWS": account_arn}, - "Effect": "Deny", - } - ], - } - - role_arn = create_role(AssumeRolePolicyDocument=json.dumps(assume_policy_doc))["Role"]["Arn"] - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - RoleARN=role_arn, - ) - - snapshot.match("error", ex.value.response) - - -@markers.aws.validated -@pytest.mark.skip(reason="The correct error is not being raised") -def test_update_with_invalid_rollback_configuration_errors( - deploy_cfn_template, snapshot, aws_client -): - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - # Test invalid alarm type - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - RollbackConfiguration={"RollbackTriggers": [{"Arn": short_uid(), "Type": "Another"}]}, - ) - snapshot.match("type_error", ex.value.response) - - # Test invalid alarm arn - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - UsePreviousTemplate=True, - Parameters=[{"ParameterKey": "TopicName", "ParameterValue": f"topic-{short_uid()}"}], - RollbackConfiguration={ - "RollbackTriggers": [ - { - "Arn": "arn:aws:cloudwatch:us-east-1:123456789012:example-name", - "Type": "AWS::CloudWatch::Alarm", - } - ] - }, - ) - - snapshot.match("arn_error", ex.value.response) - - -@markers.aws.validated -@pytest.mark.skip(reason="The update value is not being applied") -def test_update_with_rollback_configuration(deploy_cfn_template, aws_client): - aws_client.cloudwatch.put_metric_alarm( - AlarmName="HighResourceUsage", - ComparisonOperator="GreaterThanThreshold", - EvaluationPeriods=1, - MetricName="CPUUsage", - Namespace="CustomNamespace", - Period=60, - Statistic="Average", - Threshold=70, - TreatMissingData="notBreaching", - ) - - alarms = aws_client.cloudwatch.describe_alarms(AlarmNames=["HighResourceUsage"]) - alarm_arn = alarms["MetricAlarms"][0]["AlarmArn"] - - rollback_configuration = { - "RollbackTriggers": [ - {"Arn": alarm_arn, "Type": "AWS::CloudWatch::Alarm"}, - ], - "MonitoringTimeInMinutes": 123, - } - - template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/sns_topic_parameter.yml") - ) - - stack = deploy_cfn_template( - template=template, - parameters={"TopicName": f"topic-{short_uid()}"}, - ) - - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template, - Parameters=[{"ParameterKey": "TopicName", "UsePreviousValue": True}], - RollbackConfiguration=rollback_configuration, - ) - - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - - config = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)["Stacks"][0][ - "RollbackConfiguration" - ] - assert config == rollback_configuration - - # cleanup - aws_client.cloudwatch.delete_alarms(AlarmNames=["HighResourceUsage"]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(["$..Stacks..ChangeSetId"]) -def test_diff_after_update(deploy_cfn_template, aws_client, snapshot): - template_1 = textwrap.dedent(""" - Resources: - SimpleParam: - Type: AWS::SSM::Parameter - Properties: - Value: before-stack-update - Type: String - """) - template_2 = textwrap.dedent(""" - Resources: - SimpleParam1: - Type: AWS::SSM::Parameter - Properties: - Value: after-stack-update - Type: String - """) - - stack = deploy_cfn_template( - template=template_1, - ) - - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack.stack_name) - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template_2, - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait(StackName=stack.stack_name) - get_template_response = aws_client.cloudformation.get_template(StackName=stack.stack_name) - snapshot.match("get-template-response", get_template_response) - - with pytest.raises(botocore.exceptions.ClientError) as exc_info: - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=template_2, - ) - snapshot.match("update-error", exc_info.value.response) - - describe_stack_response = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name) - assert describe_stack_response["Stacks"][0]["StackStatus"] == "UPDATE_COMPLETE" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.snapshot.json deleted file mode 100644 index 1b15733a652eb..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.snapshot.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_resource_types": { - "recorded-date": "19-11-2022, 14:34:18", - "recorded-content": { - "invalid_type_error": { - "Error": { - "Code": "ValidationError", - "Message": "Resource type AWS::SNS::Topic is not allowed by parameter ResourceTypes [AWS::EC2:*]", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "resource_not_allowed": { - "Error": { - "Code": "ValidationError", - "Message": "Resource type AWS::SNS::Topic is not allowed by parameter ResourceTypes [AWS::EC2::*]", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_basic_update": { - "recorded-date": "21-11-2022, 08:27:37", - "recorded-content": { - "update_response": { - "StackId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_template_error": { - "recorded-date": "21-11-2022, 08:57:45", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Either Template URL or Template Body must be specified.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_parameters_error_update": { - "recorded-date": "21-11-2022, 09:45:22", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "recorded-date": "21-11-2022, 10:38:33", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_role_without_permissions": { - "recorded-date": "21-11-2022, 14:14:52", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Role arn::iam::111111111111:role/role-fb405076 is invalid or cannot be assumed", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": { - "recorded-date": "21-11-2022, 15:36:32", - "recorded-content": { - "type_error": { - "Error": { - "Code": "ValidationError", - "Message": "Rollback Trigger Type not supported", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "arn_error": { - "Error": { - "Code": "ValidationError", - "Message": "RelativeId of a Rollback Trigger's ARN is incorrect", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_diff_after_update": { - "recorded-date": "09-04-2024, 06:19:23", - "recorded-content": { - "get-template-response": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "\nResources:\n SimpleParam1:\n Type: AWS::SSM::Parameter\n Properties:\n Value: after-stack-update\n Type: String\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "update-error": { - "Error": { - "Code": "ValidationError", - "Message": "No updates are to be performed.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.validation.json deleted file mode 100644 index 4723c7f6aae06..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_basic_update": { - "last_validated_date": "2022-11-21T07:27:37+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_diff_after_update": { - "last_validated_date": "2024-04-09T06:19:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_no_template_error": { - "last_validated_date": "2022-11-21T07:57:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_invalid_rollback_configuration_errors": { - "last_validated_date": "2022-11-21T14:36:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_previous_parameter_value": { - "last_validated_date": "2022-11-21T09:38:33+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_resource_types": { - "last_validated_date": "2022-11-19T13:34:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_update_stack.py::test_update_with_role_without_permissions": { - "last_validated_date": "2022-11-21T13:14:52+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py deleted file mode 100644 index 724cb12eb98f5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py +++ /dev/null @@ -1,83 +0,0 @@ -import json - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -pytestmark = pytest.mark.skip("CFNV2:Validation") - - -@markers.aws.validated -@pytest.mark.parametrize( - "outputs", - [ - { - "MyOutput": { - "Value": None, - }, - }, - { - "MyOutput": { - "Value": None, - "AnotherValue": None, - }, - }, - { - "MyOutput": {}, - }, - ], - ids=["none-value", "missing-def", "multiple-nones"], -) -def test_invalid_output_structure(deploy_cfn_template, snapshot, aws_client, outputs): - template = { - "Resources": { - "Foo": { - "Type": "AWS::SNS::Topic", - }, - }, - "Outputs": outputs, - } - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template(template=json.dumps(template)) - - snapshot.match("validation-error", e.value.response) - - -@markers.aws.validated -def test_missing_resources_block(deploy_cfn_template, snapshot, aws_client): - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template(template=json.dumps({})) - - snapshot.match("validation-error", e.value.response) - - -@markers.aws.validated -@pytest.mark.parametrize( - "properties", - [ - { - "Properties": {}, - }, - { - "Type": "AWS::SNS::Topic", - "Invalid": 10, - }, - ], - ids=[ - "missing-type", - "invalid-key", - ], -) -def test_resources_blocks(deploy_cfn_template, snapshot, aws_client, properties): - template = {"Resources": {"A": properties}} - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template(template=json.dumps(template)) - - snapshot.match("validation-error", e.value.response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.snapshot.json deleted file mode 100644 index 3a5eeb52ded32..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.snapshot.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[none-value]": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "[/Outputs/MyOutput/Value] 'null' values are not allowed in templates", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[missing-def]": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "[/Outputs/MyOutput/Value] 'null' values are not allowed in templates", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[multiple-nones]": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Outputs member must contain a Value object", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_missing_resources_block": { - "recorded-date": "31-05-2024, 14:53:31", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: At least one Resources member must be defined.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[missing-type]": { - "recorded-date": "31-05-2024, 14:53:32", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: [/Resources/A] Every Resources object must contain a Type member.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[invalid-key]": { - "recorded-date": "31-05-2024, 14:53:32", - "recorded-content": { - "validation-error": { - "Error": { - "Code": "ValidationError", - "Message": "Invalid template resource property 'Invalid'", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.validation.json deleted file mode 100644 index e2041c42e47d1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[missing-def]": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[multiple-nones]": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_invalid_output_structure[none-value]": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_missing_resources_block": { - "last_validated_date": "2024-05-31T14:53:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[invalid-key]": { - "last_validated_date": "2024-05-31T14:53:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/api/test_validations.py::test_resources_blocks[missing-type]": { - "last_validated_date": "2024-05-31T14:53:32+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/__init__.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py deleted file mode 100644 index 403c7c0b08baf..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestResourceAttributes: - @pytest.mark.skip(reason="failing on unresolved attributes is not enabled yet") - @markers.snapshot.skip_snapshot_verify - @markers.aws.validated - def test_invalid_getatt_fails(self, aws_client, deploy_cfn_template, snapshot): - """ - Check how CloudFormation behaves on invalid attribute names for resources in a Fn::GetAtt - - Not yet completely correct yet since this should actually initiate a rollback and the stack resource status should be set accordingly - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_invalid_getatt.yaml", - ) - ) - stack_events = exc_info.value.events - snapshot.match("stack_events", {"events": stack_events}) - - @markers.aws.validated - def test_dependency_on_attribute_with_dot_notation( - self, deploy_cfn_template, aws_client, snapshot - ): - """ - Test that a resource can depend on another resource's attribute with dot notation - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_getatt_dot_dependency.yml", - ) - ) - snapshot.match("outputs", deployment.outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.snapshot.json deleted file mode 100644 index 8e699f7013c15..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.snapshot.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": { - "recorded-date": "01-08-2023, 11:54:31", - "recorded-content": { - "stack_events": { - "events": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "[Error] /Outputs/InvalidOutput/Value/Fn::GetAtt: Resource type AWS::SSM::Parameter does not support attribute {Invalid}. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": { - "recorded-date": "21-03-2024, 21:10:29", - "recorded-content": { - "outputs": { - "DeadArn": "arn::sqs::111111111111:" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.validation.json deleted file mode 100644 index 6a74c8a6ddc2d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_dependency_on_attribute_with_dot_notation": { - "last_validated_date": "2024-03-21T21:10:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_attributes.py::TestResourceAttributes::test_invalid_getatt_fails": { - "last_validated_date": "2023-08-01T09:54:31+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py deleted file mode 100644 index 21d8af81371bc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py +++ /dev/null @@ -1,494 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -THIS_DIR = os.path.dirname(__file__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestCloudFormationConditions: - @markers.aws.validated - def test_simple_condition_evaluation_deploys_resource( - self, aws_client, deploy_cfn_template, cleanups - ): - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/simple-condition.yaml" - ), - parameters={"OptionParameter": "option-a", "TopicName": topic_name}, - ) - # verify that CloudFormation includes the resource - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - assert stack_resources["StackResources"] - - # verify actual resource deployment - assert [ - t - for t in aws_client.sns.get_paginator("list_topics") - .paginate() - .build_full_result()["Topics"] - if topic_name in t["TopicArn"] - ] - - @markers.aws.validated - def test_simple_condition_evaluation_doesnt_deploy_resource( - self, aws_client, deploy_cfn_template, cleanups - ): - """Note: Conditions allow us to deploy stacks that won't actually contain any deployed resources""" - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/simple-condition.yaml" - ), - parameters={"OptionParameter": "option-b", "TopicName": topic_name}, - ) - # verify that CloudFormation ignores the resource - aws_client.cloudformation.describe_stack_resources(StackName=deployment.stack_id) - - # FIXME: currently broken in localstack - # assert stack_resources['StackResources'] == [] - - # verify actual resource deployment - assert [ - t for t in aws_client.sns.list_topics()["Topics"] if topic_name in t["TopicArn"] - ] == [] - - @pytest.mark.parametrize( - "should_set_custom_name", - ["yep", "nope"], - ) - @markers.aws.validated - def test_simple_intrinsic_fn_condition_evaluation( - self, aws_client, deploy_cfn_template, should_set_custom_name - ): - """ - Tests a simple Fn::If condition evaluation - - The conditional ShouldSetCustomName (yep | nope) switches between an autogenerated and a predefined name for the topic - - FIXME: this should also work with the simple-intrinsic-condition-name-conflict.yaml template where the ID of the condition and the ID of the parameter are the same(!). - It is currently broken in LocalStack though - """ - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/simple-intrinsic-condition.yaml" - ), - parameters={ - "TopicName": topic_name, - "ShouldSetCustomName": should_set_custom_name, - }, - ) - # verify that the topic has the correct name - topic_arn = deployment.outputs["TopicArn"] - if should_set_custom_name == "yep": - assert topic_name in topic_arn - else: - assert topic_name not in topic_arn - - @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - def test_dependent_ref(self, aws_client, snapshot): - """ - Tests behavior of a stack with 2 resources where one depends on the other. - The referenced resource won't be deployed due to its condition evaluating to false, so the ref can't be resolved. - - This immediately leads to an error. - """ - topic_name = f"test-topic-{short_uid()}" - ssm_param_name = f"test-param-{short_uid()}" - - stack_name = f"test-condition-ref-stack-{short_uid()}" - changeset_name = "initial" - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - ChangeSetType="CREATE", - TemplateBody=load_file( - os.path.join(THIS_DIR, "../../../../../templates/conditions/ref-condition.yaml") - ), - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "SsmParamName", "ParameterValue": ssm_param_name}, - {"ParameterKey": "OptionParameter", "ParameterValue": "option-b"}, - ], - ) - snapshot.match("dependent_ref_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - def test_dependent_ref_intrinsic_fn_condition(self, aws_client, deploy_cfn_template): - """ - Checks behavior of un-refable resources - """ - topic_name = f"test-topic-{short_uid()}" - ssm_param_name = f"test-param-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../../../templates/conditions/ref-condition-intrinsic-condition.yaml", - ), - parameters={ - "TopicName": topic_name, - "SsmParamName": ssm_param_name, - "OptionParameter": "option-b", - }, - ) - - @markers.aws.validated - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - def test_dependent_ref_with_macro( - self, aws_client, deploy_cfn_template, lambda_su_role, cleanups - ): - """ - specifying option-b would normally lead to an error without the macro because of the unresolved ref. - Because the macro replaced the resources though, the test passes. - We've therefore shown that conditions aren't fully evaluated before the transformations - - Related findings: - * macros are not allowed to transform Parameters (macro invocation by CFn will fail in this case) - - """ - - log_group_name = f"test-log-group-{short_uid()}" - aws_client.logs.create_log_group(logGroupName=log_group_name) - - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/ref-condition-macro-def.yaml" - ), - parameters={ - "FnRole": lambda_su_role, - "LogGroupName": log_group_name, - "LogRoleARN": lambda_su_role, - }, - ) - - topic_name = f"test-topic-{short_uid()}" - ssm_param_name = f"test-param-{short_uid()}" - stack_name = f"test-condition-ref-macro-stack-{short_uid()}" - changeset_name = "initial" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - ChangeSetType="CREATE", - TemplateBody=load_file( - os.path.join( - THIS_DIR, "../../../../../templates/conditions/ref-condition-macro.yaml" - ) - ), - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "SsmParamName", "ParameterValue": ssm_param_name}, - {"ParameterKey": "OptionParameter", "ParameterValue": "option-b"}, - ], - ) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=changeset_name, StackName=stack_name - ) - - @pytest.mark.parametrize( - ["env_type", "should_create_bucket", "should_create_policy"], - [ - ("test", False, False), - ("test", True, False), - ("prod", False, False), - ("prod", True, True), - ], - ids=[ - "test-nobucket-nopolicy", - "test-bucket-nopolicy", - "prod-nobucket-nopolicy", - "prod-bucket-policy", - ], - ) - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - @markers.aws.validated - def test_nested_conditions( - self, - aws_client, - deploy_cfn_template, - cleanups, - env_type, - should_create_bucket, - should_create_policy, - snapshot, - ): - """ - Tests the case where a condition references another condition - - EnvType == "prod" && BucketName != "" ==> creates bucket + policy - EnvType == "test" && BucketName != "" ==> creates bucket only - EnvType == "test" && BucketName == "" ==> no resource created - EnvType == "prod" && BucketName == "" ==> no resource created - """ - bucket_name = f"ls-test-bucket-{short_uid()}" if should_create_bucket else "" - stack_name = f"condition-test-stack-{short_uid()}" - changeset_name = "initial" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - if bucket_name: - snapshot.add_transformer(snapshot.transform.regex(bucket_name, "")) - snapshot.add_transformer(snapshot.transform.regex(stack_name, "")) - - template = load_file( - os.path.join(THIS_DIR, "../../../../../templates/conditions/nested-conditions.yaml") - ) - create_cs_result = aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=changeset_name, - TemplateBody=template, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "EnvType", "ParameterValue": env_type}, - {"ParameterKey": "BucketName", "ParameterValue": bucket_name}, - ], - ) - snapshot.match("create_cs_result", create_cs_result) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - ChangeSetName=changeset_name, StackName=stack_name - ) - - describe_changeset_result = aws_client.cloudformation.describe_change_set( - ChangeSetName=changeset_name, StackName=stack_name - ) - snapshot.match("describe_changeset_result", describe_changeset_result) - aws_client.cloudformation.execute_change_set( - ChangeSetName=changeset_name, StackName=stack_name - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - stack_resources = aws_client.cloudformation.describe_stack_resources(StackName=stack_name) - if should_create_policy: - stack_policy = [ - sr - for sr in stack_resources["StackResources"] - if sr["ResourceType"] == "AWS::S3::BucketPolicy" - ][0] - snapshot.add_transformer( - snapshot.transform.regex(stack_policy["PhysicalResourceId"], ""), - priority=-1, - ) - - snapshot.match("stack_resources", stack_resources) - stack_events = aws_client.cloudformation.describe_stack_events(StackName=stack_name) - snapshot.match("stack_events", stack_events) - describe_stack_result = aws_client.cloudformation.describe_stacks(StackName=stack_name) - snapshot.match("describe_stack_result", describe_stack_result) - - # manual assertions - - # check that bucket exists - try: - aws_client.s3.head_bucket(Bucket=bucket_name) - bucket_exists = True - except Exception: - bucket_exists = False - - assert bucket_exists == should_create_bucket - - if bucket_exists: - # check if a policy exists on the bucket - try: - aws_client.s3.get_bucket_policy(Bucket=bucket_name) - bucket_policy_exists = True - except Exception: - bucket_policy_exists = False - - assert bucket_policy_exists == should_create_policy - - @pytest.mark.skipif(condition=not is_aws_cloud(), reason="not supported yet") - @markers.aws.validated - def test_output_reference_to_skipped_resource(self, deploy_cfn_template, aws_client, snapshot): - """test what happens to outputs that reference a resource that isn't deployed due to a falsy condition""" - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/conditions/ref-condition-output.yaml" - ), - parameters={ - "OptionParameter": "option-b", - }, - ) - snapshot.match("unresolved_resource_reference_exception", e.value.response) - - @pytest.mark.aws_validated - @pytest.mark.parametrize("create_parameter", ("true", "false"), ids=("create", "no-create")) - def test_conditional_att_to_conditional_resources(self, deploy_cfn_template, create_parameter): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_if_attribute_none.yml" - ) - - deployed = deploy_cfn_template( - template_path=template_path, - parameters={"CreateParameter": create_parameter}, - ) - - if create_parameter == "false": - assert deployed.outputs["Result"] == "Value1" - else: - assert deployed.outputs["Result"] == "Value2" - - # def test_updating_only_conditions_during_stack_update(self): - # ... - - # def test_condition_with_unsupported_intrinsic_functions(self): - # ... - - @pytest.mark.parametrize( - ["should_use_fallback", "match_value"], - [ - (None, "FallbackParamValue"), - ("false", "DefaultParamValue"), - # CFNV2:Other - # ("true", "FallbackParamValue"), - ], - ) - @markers.aws.validated - def test_dependency_in_non_evaluated_if_branch( - self, deploy_cfn_template, aws_client, should_use_fallback, match_value - ): - parameters = ( - {"ShouldUseFallbackParameter": should_use_fallback} if should_use_fallback else {} - ) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_if_conditional_reference.yaml", - ), - parameters=parameters, - ) - param = aws_client.ssm.get_parameter(Name=stack.outputs["ParameterName"]) - assert param["Parameter"]["Value"] == match_value - - @markers.aws.validated - def test_sub_in_conditions(self, deploy_cfn_template, aws_client): - region = aws_client.cloudformation.meta.region_name - topic_prefix = f"test-topic-{short_uid()}" - suffix = short_uid() - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/conditions/intrinsic-functions-in-conditions.yaml", - ), - parameters={ - "TopicName": f"{topic_prefix}-{region}", - "TopicPrefix": topic_prefix, - "TopicNameWithSuffix": f"{topic_prefix}-{region}-{suffix}", - "TopicNameSuffix": suffix, - }, - ) - - topic_arn = stack.outputs["TopicRef"] - aws_client.sns.get_topic_attributes(TopicArn=topic_arn) - assert topic_arn.split(":")[-1] == f"{topic_prefix}-{region}" - - topic_arn_with_suffix = stack.outputs["TopicWithSuffixRef"] - aws_client.sns.get_topic_attributes(TopicArn=topic_arn_with_suffix) - assert topic_arn_with_suffix.split(":")[-1] == f"{topic_prefix}-{region}-{suffix}" - - @markers.aws.validated - @pytest.mark.parametrize("env,region", [("dev", "us-west-2"), ("production", "us-east-1")]) - def test_conditional_in_conditional(self, env, region, deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/conditions/conditional-in-conditional.yml", - ), - parameters={ - "SelectedRegion": region, - "Environment": env, - }, - ) - - if env == "production" and region == "us-east-1": - assert stack.outputs["Result"] == "true" - else: - assert stack.outputs["Result"] == "false" - - @markers.aws.validated - def test_conditional_with_select(self, deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/conditions/conditional-with-select.yml", - ), - ) - - managed_policy_arn = stack.outputs["PolicyArn"] - assert aws_client.iam.get_policy(PolicyArn=managed_policy_arn) - - @markers.aws.validated - def test_condition_on_outputs(self, deploy_cfn_template, aws_client): - """ - The stack has 2 outputs. - Each is gated by a different condition value ("test" vs. "prod"). - Only one of them should be returned for the stack outputs - """ - nested_bucket_name = f"test-bucket-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/nested-stack-conditions.nested.yaml", - ), - parameters={ - "BucketBaseName": nested_bucket_name, - "Mode": "prod", - }, - ) - assert "TestBucket" not in stack.outputs - assert stack.outputs["ProdBucket"] == f"{nested_bucket_name}-prod" - assert aws_client.s3.head_bucket(Bucket=stack.outputs["ProdBucket"]) - - @markers.aws.validated - def test_update_conditions(self, deploy_cfn_template, aws_client): - original_bucket_name = f"test-bucket-{short_uid()}" - stack_name = f"test-update-conditions-{short_uid()}" - deploy_cfn_template( - stack_name=stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_condition_update_1.yml" - ), - parameters={"OriginalBucketName": original_bucket_name}, - ) - assert aws_client.s3.head_bucket(Bucket=original_bucket_name) - - bucket_1 = f"test-bucket-1-{short_uid()}" - bucket_2 = f"test-bucket-2-{short_uid()}" - - deploy_cfn_template( - stack_name=stack_name, - is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_condition_update_2.yml" - ), - parameters={ - "OriginalBucketName": original_bucket_name, - "FirstBucket": bucket_1, - "SecondBucket": bucket_2, - }, - ) - - assert aws_client.s3.head_bucket(Bucket=original_bucket_name) - assert aws_client.s3.head_bucket(Bucket=bucket_1) - with pytest.raises(aws_client.s3.exceptions.ClientError): - aws_client.s3.head_bucket(Bucket=bucket_2) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.snapshot.json deleted file mode 100644 index 358e26e2e16a7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.snapshot.json +++ /dev/null @@ -1,763 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": { - "recorded-date": "26-06-2023, 14:20:49", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": { - "recorded-date": "26-06-2023, 14:21:54", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "Bucket", - "ResourceType": "AWS::S3::Bucket", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_COMPLETE-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "test" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": { - "recorded-date": "26-06-2023, 14:22:58", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": { - "recorded-date": "26-06-2023, 14:24:03", - "recorded-content": { - "create_cs_result": { - "Id": "arn::cloudformation::111111111111:changeSet/", - "StackId": "arn::cloudformation::111111111111:stack//", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_changeset_result": { - "Capabilities": [], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "ChangeSetName": "", - "Changes": [ - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "Bucket", - "ResourceType": "AWS::S3::Bucket", - "Scope": [] - }, - "Type": "Resource" - }, - { - "ResourceChange": { - "Action": "Add", - "Details": [], - "LogicalResourceId": "Policy", - "ResourceType": "AWS::S3::BucketPolicy", - "Scope": [] - }, - "Type": "Resource" - } - ], - "CreationTime": "datetime", - "ExecutionStatus": "AVAILABLE", - "IncludeNestedStacks": false, - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Status": "CREATE_COMPLETE", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_events": { - "StackEvents": [ - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Policy-CREATE_COMPLETE-date", - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceProperties": { - "Bucket": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn::s3:::/*" - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - } - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Policy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceProperties": { - "Bucket": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn::s3:::/*" - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Policy-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Policy", - "PhysicalResourceId": "", - "ResourceProperties": { - "Bucket": "", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": [ - "s3:GetObject" - ], - "Resource": [ - "arn::s3:::/*" - ], - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ] - } - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::BucketPolicy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_COMPLETE-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "Resource creation Initiated", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "Bucket-CREATE_IN_PROGRESS-date", - "LogicalResourceId": "Bucket", - "PhysicalResourceId": "", - "ResourceProperties": { - "BucketName": "" - }, - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceType": "AWS::S3::Bucket", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "CREATE_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "REVIEW_IN_PROGRESS", - "ResourceStatusReason": "User Initiated", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stack_result": { - "Stacks": [ - { - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "BucketName", - "ParameterValue": "" - }, - { - "ParameterKey": "EnvType", - "ParameterValue": "prod" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": { - "recorded-date": "26-06-2023, 14:18:26", - "recorded-content": { - "dependent_ref_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Unresolved resource dependencies [MyTopic] in the Resources block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": { - "recorded-date": "27-06-2023, 00:43:18", - "recorded-content": { - "unresolved_resource_reference_exception": { - "Error": { - "Code": "ValidationError", - "Message": "Unresolved resource dependencies [MyTopic] in the Outputs block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.validation.json deleted file mode 100644 index e285748924d8a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_dependent_ref": { - "last_validated_date": "2023-06-26T12:18:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-bucket-policy]": { - "last_validated_date": "2023-06-26T12:24:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[prod-nobucket-nopolicy]": { - "last_validated_date": "2023-06-26T12:22:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-bucket-nopolicy]": { - "last_validated_date": "2023-06-26T12:21:54+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_nested_conditions[test-nobucket-nopolicy]": { - "last_validated_date": "2023-06-26T12:20:49+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_output_reference_to_skipped_resource": { - "last_validated_date": "2023-06-26T22:43:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_conditions.py::TestCloudFormationConditions::test_update_conditions": { - "last_validated_date": "2024-06-18T19:43:43+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py deleted file mode 100644 index a088355fd966a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py +++ /dev/null @@ -1,266 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -THIS_DIR = os.path.dirname(__file__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.snapshot.skip_snapshot_verify -class TestCloudFormationMappings: - @markers.aws.validated - def test_simple_mapping_working(self, aws_client, deploy_cfn_template): - """ - A very simple test to deploy a resource with a name depending on a value that needs to be looked up from the mapping - """ - topic_name = f"test-topic-{short_uid()}" - deployment = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping.yaml" - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "A", - }, - ) - # verify that CloudFormation includes the resource - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - assert stack_resources["StackResources"] - - expected_topic_name = f"{topic_name}-suffix-a" - - # verify actual resource deployment - assert [ - t - for t in aws_client.sns.get_paginator("list_topics") - .paginate() - .build_full_result()["Topics"] - if expected_topic_name in t["TopicArn"] - ] - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_with_nonexisting_key(self, aws_client, cleanups, snapshot): - """ - Tries to deploy a resource with a dependency on a mapping key - which is not included in the Mappings section and thus can't be resolved - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join(THIS_DIR, "../../../../../templates/mappings/simple-mapping.yaml") - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "C"}, - ], - ) - snapshot.match("mapping_nonexisting_key_exc", e.value.response) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.only_localstack - def test_async_mapping_error_first_level(self, deploy_cfn_template): - """ - We don't (yet) support validating mappings synchronously in `create_changeset` like AWS does, however - we don't fail with a good error message at all. This test ensures that the deployment fails with a - nicer error message than a Python traceback about "`None` has no attribute `get`". - """ - topic_name = f"test-topic-{short_uid()}" - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../../../templates/mappings/simple-mapping.yaml", - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "C", - }, - ) - - assert "Cannot find map key 'C' in mapping 'TopicSuffixMap'" in str(exc_info.value) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.only_localstack - def test_async_mapping_error_second_level(self, deploy_cfn_template): - """ - Similar to the `test_async_mapping_error_first_level` test above, but - checking the second level of mapping lookup - """ - topic_name = f"test-topic-{short_uid()}" - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, - "../../../../../templates/mappings/simple-mapping.yaml", - ), - parameters={ - "TopicName": topic_name, - "TopicNameSuffixSelector": "A", - "TopicAttributeSelector": "NotValid", - }, - ) - - assert "Cannot find map key 'NotValid' in mapping 'TopicSuffixMap' under key 'A'" in str( - exc_info.value - ) - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_with_invalid_refs(self, aws_client, deploy_cfn_template, cleanups, snapshot): - """ - The Mappings section can only include static elements (strings and lists). - In this test one value is instead a `Ref` which should be rejected by the service - - Also note the overlap with the `test_mapping_with_nonexisting_key` case here. - Even though we specify a non-existing key here again (`C`), the returned error is for the invalid structure. - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping-invalid-ref.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "C"}, - {"ParameterKey": "TopicNameSuffix", "ParameterValue": "suffix-c"}, - ], - ) - snapshot.match("mapping_invalid_ref_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_maximum_nesting_depth(self, aws_client, cleanups, snapshot): - """ - Tries to deploy a template containing a mapping with a nesting depth of 3. - The maximum depth is 2 so it should fail - - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping-nesting-depth.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "A"}, - ], - ) - snapshot.match("mapping_maximum_level_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.skip(reason="not implemented") - def test_mapping_minimum_nesting_depth(self, aws_client, cleanups, snapshot): - """ - Tries to deploy a template containing a mapping with a nesting depth of 1. - The required depth is 2, so it should fail for a single level - """ - topic_name = f"test-topic-{short_uid()}" - stack_name = f"test-stack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - template_body = load_file( - os.path.join( - THIS_DIR, "../../../../../templates/mappings/simple-mapping-single-level.yaml" - ) - ) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="initial", - TemplateBody=template_body, - ChangeSetType="CREATE", - Parameters=[ - {"ParameterKey": "TopicName", "ParameterValue": topic_name}, - {"ParameterKey": "TopicNameSuffixSelector", "ParameterValue": "A"}, - ], - ) - snapshot.match("mapping_minimum_level_exc", e.value.response) - - @markers.aws.validated - @pytest.mark.parametrize( - "map_key,should_error", - [ - ("A", False), - ("B", True), - ], - ids=["should-deploy", "should-not-deploy"], - ) - def test_mapping_ref_map_key(self, deploy_cfn_template, aws_client, map_key, should_error): - topic_name = f"topic-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/mappings/mapping-ref-map-key.yaml" - ), - parameters={ - "MapName": "MyMap", - "MapKey": map_key, - "TopicName": topic_name, - }, - ) - - topic_arn = stack.outputs.get("TopicArn") - if should_error: - assert topic_arn is None - else: - assert topic_arn is not None - - aws_client.sns.get_topic_attributes(TopicArn=topic_arn) - - @markers.aws.validated - def test_aws_refs_in_mappings(self, deploy_cfn_template, account_id): - """ - This test asserts that Pseudo references aka "AWS::" are supported inside a mapping inside a Conditional. - It's worth remembering that even with references being supported, AWS rejects names that are not alphanumeric - in Mapping name or the second level key. - """ - stack_name = f"Stack{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_DIR, "../../../../../templates/mappings/mapping-aws-ref-map-key.yaml" - ), - stack_name=stack_name, - template_mapping={"StackName": stack_name}, - ) - assert stack.outputs.get("TopicArn") diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.snapshot.json deleted file mode 100644 index b5ecf4d26a841..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.snapshot.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": { - "recorded-date": "12-06-2023, 16:47:23", - "recorded-content": { - "mapping_nonexisting_key_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template error: Unable to get mapping for TopicSuffixMap::C::Suffix", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": { - "recorded-date": "12-06-2023, 16:47:24", - "recorded-content": { - "mapping_invalid_ref_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Mappings attribute must be a String or a List.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": { - "recorded-date": "12-06-2023, 16:47:24", - "recorded-content": { - "mapping_maximum_level_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Mappings attribute must be a String or a List.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": { - "recorded-date": "12-06-2023, 16:47:25", - "recorded-content": { - "mapping_minimum_level_exc": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Every Mappings member A must be a map", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.validation.json deleted file mode 100644 index b66abfb0050a0..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_aws_refs_in_mappings": { - "last_validated_date": "2024-10-15T17:22:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_maximum_nesting_depth": { - "last_validated_date": "2023-06-12T14:47:24+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_minimum_nesting_depth": { - "last_validated_date": "2023-06-12T14:47:25+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-deploy]": { - "last_validated_date": "2024-10-17T22:40:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_ref_map_key[should-not-deploy]": { - "last_validated_date": "2024-10-17T22:41:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_invalid_refs": { - "last_validated_date": "2023-06-12T14:47:24+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_mappings.py::TestCloudFormationMappings::test_mapping_with_nonexisting_key": { - "last_validated_date": "2023-06-12T14:47:23+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py deleted file mode 100644 index d89ae634ae003..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py +++ /dev/null @@ -1,131 +0,0 @@ -import json -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestDependsOn: - @pytest.mark.skip(reason="not supported yet") - @markers.aws.validated - def test_depends_on_with_missing_reference( - self, deploy_cfn_template, aws_client, cleanups, snapshot - ): - stack_name = f"test-stack-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/engine/cfn_dependson_nonexisting_resource.yaml", - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - with pytest.raises(aws_client.cloudformation.exceptions.ClientError) as e: - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName="init", - ChangeSetType="CREATE", - TemplateBody=load_file(template_path), - ) - snapshot.match("depends_on_nonexisting_exception", e.value.response) - - -class TestFnSub: - # TODO: add test for list sub without a second argument (i.e. the list) - # => Template error: One or more Fn::Sub intrinsic functions don't specify expected arguments. Specify a string as first argument, and an optional second argument to specify a mapping of values to replace in the string - - @markers.aws.validated - def test_fn_sub_cases(self, deploy_cfn_template, aws_client, snapshot): - ssm_parameter_name = f"test-param-{short_uid()}" - snapshot.add_transformer( - snapshot.transform.regex(ssm_parameter_name, "") - ) - snapshot.add_transformer( - snapshot.transform.key_value( - "UrlSuffixPseudoParam", "", reference_replacement=False - ) - ) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/engine/cfn_fn_sub.yaml" - ), - parameters={"ParameterName": ssm_parameter_name}, - ) - - snapshot.match("outputs", deployment.outputs) - - @markers.aws.validated - def test_non_string_parameter_in_sub(self, deploy_cfn_template, aws_client, snapshot): - ssm_parameter_name = f"test-param-{short_uid()}" - snapshot.add_transformer( - snapshot.transform.regex(ssm_parameter_name, "") - ) - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_number_in_sub.yml" - ), - parameters={"ParameterName": ssm_parameter_name}, - ) - - get_param_res = aws_client.ssm.get_parameter(Name=ssm_parameter_name)["Parameter"] - snapshot.match("get-parameter-result", get_param_res) - - -@pytest.mark.skip(reason="CFNV2:Validation") -@markers.aws.validated -def test_useful_error_when_invalid_ref(deploy_cfn_template, snapshot): - """ - When trying to resolve a non-existent !Ref, make sure the error message includes the name of the !Ref - to clarify which !Ref cannot be resolved. - """ - logical_resource_id = "Topic" - ref_name = "InvalidRef" - - template = json.dumps( - { - "Resources": { - logical_resource_id: { - "Type": "AWS::SNS::Topic", - "Properties": { - "Name": { - "Ref": ref_name, - }, - }, - } - } - } - ) - - with pytest.raises(ClientError) as exc_info: - deploy_cfn_template(template=template) - - snapshot.match("validation_error", exc_info.value.response) - - -@markers.aws.validated -def test_resolve_transitive_placeholders_in_strings(deploy_cfn_template, aws_client, snapshot): - queue_name = f"q-{short_uid()}" - parameter_ver = f"v{short_uid()}" - stack_name = f"stack-{short_uid()}" - stack = deploy_cfn_template( - stack_name=stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/legacy_transitive_ref.yaml" - ), - max_wait=300 if is_aws_cloud() else 10, - parameters={"QueueName": queue_name, "Qualifier": parameter_ver}, - ) - tags = aws_client.sqs.list_queue_tags(QueueUrl=stack.outputs["QueueURL"]) - snapshot.add_transformer( - snapshot.transform.regex(r"/cdk-bootstrap/(\w+)/", "/cdk-bootstrap/.../") - ) - snapshot.match("tags", tags) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.snapshot.json deleted file mode 100644 index c17fb974377b0..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.snapshot.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": { - "recorded-date": "10-07-2023, 15:22:26", - "recorded-content": { - "depends_on_nonexisting_exception": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Unresolved resource dependencies [NonExistingResource] in the Resources block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_fn_sub_cases": { - "recorded-date": "23-08-2023, 20:41:02", - "recorded-content": { - "outputs": { - "ListRefGetAtt": "unimportant", - "ListRefGetAttMapping": "unimportant", - "ListRefMultipleMix": "Param1Value--Param1Value", - "ListRefParam": "Param1Value", - "ListRefPseudoParam": "", - "ListRefResourceDirect": "Param1Value", - "ListRefResourceMappingRef": "Param1Value", - "ListStatic": "this is a static string", - "StringRefGetAtt": "unimportant", - "StringRefMultiple": "Param1Value - Param1Value", - "StringRefParam": "Param1Value", - "StringRefPseudoParam": "", - "StringRefResource": "Param1Value", - "StringStatic": "this is a static string", - "UrlSuffixPseudoParam": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_useful_error_when_invalid_ref": { - "recorded-date": "28-05-2024, 11:42:58", - "recorded-content": { - "validation_error": { - "Error": { - "Code": "ValidationError", - "Message": "Template format error: Unresolved resource dependencies [InvalidRef] in the Resources block of the template", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": { - "recorded-date": "18-06-2024, 19:55:48", - "recorded-content": { - "tags": { - "Tags": { - "test": "arn::ssm::111111111111:parameter/cdk-bootstrap/.../version" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": { - "recorded-date": "17-10-2024, 22:49:56", - "recorded-content": { - "get-parameter-result": { - "ARN": "arn::ssm::111111111111:parameter/", - "DataType": "text", - "LastModifiedDate": "datetime", - "Name": "", - "Type": "String", - "Value": "my number is 3", - "Version": 1 - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.validation.json deleted file mode 100644 index b2edacb2b077b..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestDependsOn::test_depends_on_with_missing_reference": { - "last_validated_date": "2023-07-10T13:22:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_fn_sub_cases": { - "last_validated_date": "2023-08-23T18:41:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::TestFnSub::test_non_string_parameter_in_sub": { - "last_validated_date": "2024-10-17T22:49:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_resolve_transitive_placeholders_in_strings": { - "last_validated_date": "2024-06-18T19:55:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/engine/test_references.py::test_useful_error_when_invalid_ref": { - "last_validated_date": "2024-05-28T11:42:58+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/__init__.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/__init__.py deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler1/api.zip b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler1/api.zip deleted file mode 100644 index 8f8c0f78f6257..0000000000000 Binary files a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler1/api.zip and /dev/null differ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler2/api.zip b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler2/api.zip deleted file mode 100644 index f45beec4a069f..0000000000000 Binary files a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/handlers/handler2/api.zip and /dev/null differ diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py deleted file mode 100644 index 5e215533958e9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_acm.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -TEST_TEMPLATE = """ -Resources: - cert1: - Type: "AWS::CertificateManager::Certificate" - Properties: - DomainName: "{{domain}}" - DomainValidationOptions: - - DomainName: "{{domain}}" - HostedZoneId: zone123 # using dummy ID for now - ValidationMethod: DNS -Outputs: - Cert: - Value: !Ref cert1 -""" - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.only_localstack -def test_cfn_acm_certificate(deploy_cfn_template, aws_client): - domain = f"domain-{short_uid()}.com" - deploy_cfn_template(template=TEST_TEMPLATE, template_mapping={"domain": domain}) - - result = aws_client.acm.list_certificates()["CertificateSummaryList"] - result = [cert for cert in result if cert["DomainName"] == domain] - assert result diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py deleted file mode 100644 index bf95868e519b1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py +++ /dev/null @@ -1,707 +0,0 @@ -import json -import os.path -from operator import itemgetter - -import pytest -import requests -from tests.aws.services.apigateway.apigateway_fixtures import api_invoke_url - -from localstack import constants -from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid -from localstack.utils.files import load_file -from localstack.utils.run import to_str -from localstack.utils.strings import to_bytes - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -TEST_LAMBDA_PYTHON_ECHO = os.path.join(PARENT_DIR, "lambda_/functions/lambda_echo.py") - -TEST_TEMPLATE_1 = """ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Parameters: - ApiName: - Type: String - IntegrationUri: - Type: String -Resources: - Api: - Type: AWS::Serverless::Api - Properties: - StageName: dev - Name: !Ref ApiName - DefinitionBody: - swagger: 2.0 - info: - version: "1.0" - title: "Public API" - basePath: /base - schemes: - - "https" - x-amazon-apigateway-binary-media-types: - - "*/*" - paths: - /test: - post: - responses: {} - x-amazon-apigateway-integration: - uri: !Ref IntegrationUri - httpMethod: "POST" - type: "http_proxy" -""" - - -# this is an `only_localstack` test because it makes use of _custom_id_ tag -@markers.aws.only_localstack -def test_cfn_apigateway_aws_integration(deploy_cfn_template, aws_client): - api_name = f"rest-api-{short_uid()}" - custom_id = short_uid() - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/apigw-awsintegration-request-parameters.yaml", - ), - parameters={ - "ApiName": api_name, - "CustomTagKey": "_custom_id_", - "CustomTagValue": custom_id, - }, - ) - - # check resources creation - apis = [ - api for api in aws_client.apigateway.get_rest_apis()["items"] if api["name"] == api_name - ] - assert len(apis) == 1 - api_id = apis[0]["id"] - - # check resources creation - resources = aws_client.apigateway.get_resources(restApiId=api_id)["items"] - assert ( - resources[0]["resourceMethods"]["GET"]["requestParameters"]["method.request.path.id"] - is False - ) - assert ( - resources[0]["resourceMethods"]["GET"]["methodIntegration"]["requestParameters"][ - "integration.request.path.object" - ] - == "method.request.path.id" - ) - - # check domains creation - domain_names = [ - domain["domainName"] for domain in aws_client.apigateway.get_domain_names()["items"] - ] - expected_domain = "cfn5632.localstack.cloud" # hardcoded value from template yaml file - assert expected_domain in domain_names - - # check basepath mappings creation - mappings = [ - mapping["basePath"] - for mapping in aws_client.apigateway.get_base_path_mappings(domainName=expected_domain)[ - "items" - ] - ] - assert len(mappings) == 1 - assert mappings[0] == "(none)" - - -@markers.aws.validated -def test_cfn_apigateway_swagger_import(deploy_cfn_template, echo_http_server_post, aws_client): - api_name = f"rest-api-{short_uid()}" - deploy_cfn_template( - template=TEST_TEMPLATE_1, - parameters={"ApiName": api_name, "IntegrationUri": echo_http_server_post}, - ) - - # get API details - apis = [ - api for api in aws_client.apigateway.get_rest_apis()["items"] if api["name"] == api_name - ] - assert len(apis) == 1 - api_id = apis[0]["id"] - - # construct API endpoint URL - url = api_invoke_url(api_id, stage="dev", path="/test") - - # invoke API endpoint, assert results - result = requests.post(url, data="test 123") - assert result.ok - content = json.loads(to_str(result.content)) - assert content["data"] == "test 123" - assert content["url"].endswith("/post") - - -@pytest.mark.skip( - reason="The v2 provider appears to instead return the correct url: " - "https://e1i3grfiws.execute-api.us-east-1.localhost.localstack.cloud/prod/" -) -@markers.aws.only_localstack -def test_url_output(httpserver, deploy_cfn_template): - httpserver.expect_request("").respond_with_data(b"", 200) - api_name = f"rest-api-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway-url-output.yaml" - ), - template_mapping={ - "api_name": api_name, - "integration_uri": httpserver.url_for("/{proxy}"), - }, - ) - - assert len(stack.outputs) == 2 - api_id = stack.outputs["ApiV1IdOutput"] - api_url = stack.outputs["ApiV1UrlOutput"] - assert api_id - assert api_url - assert api_id in api_url - - assert f"https://{api_id}.execute-api.{constants.LOCALHOST_HOSTNAME}:4566" in api_url - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.get-method-post.methodIntegration.connectionType", # TODO: maybe because this is a MOCK integration - ] -) -def test_cfn_with_apigateway_resources(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template35.yaml" - ) - ) - apis = [ - api - for api in aws_client.apigateway.get_rest_apis()["items"] - if api["name"] == "celeste-Gateway-local" - ] - assert len(apis) == 1 - api_id = apis[0]["id"] - - resources = [ - res - for res in aws_client.apigateway.get_resources(restApiId=api_id)["items"] - if res.get("pathPart") == "account" - ] - - assert len(resources) == 1 - - resp = aws_client.apigateway.get_method( - restApiId=api_id, resourceId=resources[0]["id"], httpMethod="POST" - ) - snapshot.match("get-method-post", resp) - - models = aws_client.apigateway.get_models(restApiId=api_id) - models["items"].sort(key=itemgetter("name")) - snapshot.match("get-models", models) - - schemas = [model["schema"] for model in models["items"]] - for schema in schemas: - # assert that we can JSON load the schema, and that the schema is a valid JSON - assert isinstance(json.loads(schema), dict) - - stack.destroy() - - # TODO: Resolve limitations with stack.destroy in v2 engine. - # apis = [ - # api - # for api in aws_client.apigateway.get_rest_apis()["items"] - # if api["name"] == "celeste-Gateway-local" - # ] - # assert not apis - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.get-resources.items..resourceMethods.ANY", # TODO: empty in AWS - ] -) -def test_cfn_deploy_apigateway_models(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.apigateway_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_models.json" - ) - ) - - api_id = stack.outputs["RestApiId"] - - resources = aws_client.apigateway.get_resources(restApiId=api_id) - resources["items"].sort(key=itemgetter("path")) - snapshot.match("get-resources", resources) - - models = aws_client.apigateway.get_models(restApiId=api_id) - models["items"].sort(key=itemgetter("name")) - snapshot.match("get-models", models) - - request_validators = aws_client.apigateway.get_request_validators(restApiId=api_id) - snapshot.match("get-request-validators", request_validators) - - for resource in resources["items"]: - if resource["path"] == "/validated": - resp = aws_client.apigateway.get_method( - restApiId=api_id, resourceId=resource["id"], httpMethod="ANY" - ) - snapshot.match("get-method-any", resp) - - # construct API endpoint URL - url = api_invoke_url(api_id, stage="local", path="/validated") - - # invoke API endpoint, assert results - valid_data = {"string_field": "string", "integer_field": 123456789} - - result = requests.post(url, json=valid_data) - assert result.ok - - # invoke API endpoint, assert results - invalid_data = {"string_field": "string"} - - result = requests.post(url, json=invalid_data) - assert result.status_code == 400 - - result = requests.get(url) - assert result.status_code == 400 - - -@markers.aws.validated -def test_cfn_deploy_apigateway_integration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.key_value("cacheNamespace")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/apigateway_integration_no_authorizer.yml", - ), - max_wait=120, - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "stack-name")) - - rest_api_id = stack.outputs["RestApiId"] - rest_api = aws_client.apigateway.get_rest_api(restApiId=rest_api_id) - snapshot.match("rest_api", rest_api) - snapshot.add_transformer(snapshot.transform.key_value("rootResourceId")) - - resource_id = stack.outputs["ResourceId"] - method = aws_client.apigateway.get_method( - restApiId=rest_api_id, resourceId=resource_id, httpMethod="GET" - ) - snapshot.match("method", method) - # TODO: snapshot the authorizer too? it's not attached to the REST API - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.resources.items..resourceMethods.GET", # TODO: after importing, AWS returns them empty? - # TODO: missing from LS response - "$.get-stage.methodSettings", - "$.get-stage.tags", - "$..binaryMediaTypes", - ] -) -def test_cfn_deploy_apigateway_from_s3_swagger( - deploy_cfn_template, snapshot, aws_client, s3_bucket -): - snapshot.add_transformer(snapshot.transform.key_value("deploymentId")) - # put the swagger file in S3 - swagger_template = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../files/pets.json") - ) - key_name = "swagger-template-pets.json" - response = aws_client.s3.put_object(Bucket=s3_bucket, Key=key_name, Body=swagger_template) - object_etag = response["ETag"] - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_integration_from_s3.yml" - ), - parameters={ - "S3BodyBucket": s3_bucket, - "S3BodyKey": key_name, - "S3BodyETag": object_etag, - }, - max_wait=120, - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "stack-name")) - - rest_api_id = stack.outputs["RestApiId"] - rest_api = aws_client.apigateway.get_rest_api(restApiId=rest_api_id) - snapshot.match("rest-api", rest_api) - - resources = aws_client.apigateway.get_resources(restApiId=rest_api_id) - resources["items"] = sorted(resources["items"], key=itemgetter("path")) - snapshot.match("resources", resources) - - get_stage = aws_client.apigateway.get_stage(restApiId=rest_api_id, stageName="local") - snapshot.match("get-stage", get_stage) - - -@markers.aws.validated -def test_cfn_apigateway_rest_api(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway.json" - ) - ) - - rs = aws_client.apigateway.get_rest_apis() - apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - assert not apis - - stack.destroy() - - stack_2 = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway.json" - ), - parameters={"Create": "True"}, - ) - rs = aws_client.apigateway.get_rest_apis() - apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - assert len(apis) == 1 - - rs = aws_client.apigateway.get_models(restApiId=apis[0]["id"]) - assert len(rs["items"]) == 3 - - stack_2.destroy() - - # TODO: Resolve limitations with stack.destroy in v2 engine. - # rs = aws_client.apigateway.get_rest_apis() - # apis = [item for item in rs["items"] if item["name"] == "DemoApi_dev"] - # assert not apis - - -@markers.aws.validated -def test_account(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_account.yml" - ) - ) - - account_info = aws_client.apigateway.get_account() - assert account_info["cloudwatchRoleArn"] == stack.outputs["RoleArn"] - - # Assert that after deletion of stack, the apigw account is not updated - stack.destroy() - aws_client.cloudformation.get_waiter("stack_delete_complete").wait(StackName=stack.stack_name) - account_info = aws_client.apigateway.get_account() - assert account_info["cloudwatchRoleArn"] == stack.outputs["RoleArn"] - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..tags.'aws:cloudformation:logical-id'", - "$..tags.'aws:cloudformation:stack-id'", - "$..tags.'aws:cloudformation:stack-name'", - ] -) -def test_update_usage_plan(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("apiId"), - snapshot.transform.key_value("stage"), - snapshot.transform.key_value("id"), - snapshot.transform.key_value("name"), - snapshot.transform.key_value("aws:cloudformation:stack-name"), - snapshot.transform.resource_name(), - ] - ) - rest_api_name = f"api-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_usage_plan.yml" - ), - parameters={"QuotaLimit": "5000", "RestApiName": rest_api_name, "TagValue": "value1"}, - ) - - usage_plan = aws_client.apigateway.get_usage_plan(usagePlanId=stack.outputs["UsagePlanId"]) - snapshot.match("usage-plan", usage_plan) - assert usage_plan["quota"]["limit"] == 5000 - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template=load_file( - os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_usage_plan.yml" - ) - ), - parameters={ - "QuotaLimit": "7000", - "RestApiName": rest_api_name, - "TagValue": "value-updated", - }, - ) - - usage_plan = aws_client.apigateway.get_usage_plan(usagePlanId=stack.outputs["UsagePlanId"]) - snapshot.match("updated-usage-plan", usage_plan) - assert usage_plan["quota"]["limit"] == 7000 - - -@markers.snapshot.skip_snapshot_verify( - paths=["$..createdDate", "$..description", "$..lastUpdatedDate", "$..tags"] -) -@markers.aws.validated -def test_update_apigateway_stage(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("deploymentId"), - snapshot.transform.key_value("aws:cloudformation:stack-name"), - snapshot.transform.resource_name(), - ] - ) - - api_name = f"api-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_update_stage.yml" - ), - parameters={"RestApiName": api_name}, - ) - api_id = stack.outputs["RestApiId"] - stage = aws_client.apigateway.get_stage(stageName="dev", restApiId=api_id) - snapshot.match("created-stage", stage) - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/apigateway_update_stage.yml" - ), - parameters={ - "Description": "updated-description", - "Method": "POST", - "RestApiName": api_name, - }, - ) - # Changes to the stage or one of the methods it depends on does not trigger a redeployment - stage = aws_client.apigateway.get_stage(stageName="dev", restApiId=api_id) - snapshot.match("updated-stage", stage) - - -@markers.aws.validated -def test_api_gateway_with_policy_as_dict(deploy_cfn_template, snapshot, aws_client): - template = """ - Parameters: - RestApiName: - Type: String - Resources: - MyApi: - Type: AWS::ApiGateway::RestApi - Properties: - Name: !Ref RestApiName - Policy: - Version: "2012-10-17" - Statement: - - Sid: AllowInvokeAPI - Action: "*" - Effect: Allow - Principal: - AWS: "*" - Resource: "*" - Outputs: - MyApiId: - Value: !Ref MyApi - """ - - rest_api_name = f"api-{short_uid()}" - stack = deploy_cfn_template( - template=template, - parameters={"RestApiName": rest_api_name}, - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "stack-name")) - - rest_api = aws_client.apigateway.get_rest_api(restApiId=stack.outputs.get("MyApiId")) - - # note: API Gateway seems to perform double-escaping of the policy document for REST APIs, if specified as dict - policy = to_bytes(rest_api["policy"]).decode("unicode_escape") - rest_api["policy"] = json.loads(policy) - - snapshot.match("rest-api", rest_api) - - -@pytest.mark.skip( - reason="CFNV2:Other lambda function fails on creation due to invalid function name" -) -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$.put-ssm-param.Tier", - "$.get-resources.items..resourceMethods.GET", - "$.get-resources.items..resourceMethods.OPTIONS", - "$..methodIntegration.cacheNamespace", - "$.get-authorizers.items..authorizerResultTtlInSeconds", - ] -) -def test_rest_api_serverless_ref_resolving( - deploy_cfn_template, snapshot, aws_client, create_parameter, create_lambda_function -): - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformers_list( - [ - snapshot.transform.resource_name(), - snapshot.transform.key_value("cacheNamespace"), - snapshot.transform.key_value("uri"), - snapshot.transform.key_value("authorizerUri"), - ] - ) - create_parameter(Name="/test-stack/testssm/random-value", Value="x-test-header", Type="String") - - fn_name = f"test-{short_uid()}" - lambda_authorizer = create_lambda_function( - func_name=fn_name, - handler_file=TEST_LAMBDA_PYTHON_ECHO, - runtime=Runtime.python3_12, - ) - - create_parameter( - Name="/test-stack/testssm/lambda-arn", - Value=lambda_authorizer["CreateFunctionResponse"]["FunctionArn"], - Type="String", - ) - - stack = deploy_cfn_template( - template=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/apigateway_serverless_api_resolving.yml", - ) - ), - parameters={"AllowedOrigin": "http://localhost:8000"}, - ) - rest_api_id = stack.outputs.get("ApiGatewayApiId") - - resources = aws_client.apigateway.get_resources(restApiId=rest_api_id) - snapshot.match("get-resources", resources) - - authorizers = aws_client.apigateway.get_authorizers(restApiId=rest_api_id) - snapshot.match("get-authorizers", authorizers) - - root_resource = resources["items"][0] - - for http_method in root_resource["resourceMethods"]: - method = aws_client.apigateway.get_method( - restApiId=rest_api_id, resourceId=root_resource["id"], httpMethod=http_method - ) - snapshot.match(f"get-method-{http_method}", method) - - -class TestServerlessApigwLambda: - @markers.aws.validated - def test_serverless_like_deployment_with_update( - self, deploy_cfn_template, aws_client, cleanups - ): - """ - Regression test for serverless. Since adding a delete handler for the "AWS::ApiGateway::Deployment" resource, - the update was failing due to the delete raising an Exception because of a still connected Stage. - - This test recreates a simple recreated deployment procedure as done by "serverless" where - `serverless deploy` actually both creates a stack and then immediately updates it. - The second UpdateStack is then caused by another `serverless deploy`, e.g. when changing the lambda configuration - """ - - # 1. deploy create - template_content = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/serverless-apigw-lambda.create.json", - ) - ) - stack_name = f"slsstack-{short_uid()}" - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - stack = aws_client.cloudformation.create_stack( - StackName=stack_name, - TemplateBody=template_content, - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - aws_client.cloudformation.get_waiter("stack_create_complete").wait( - StackName=stack["StackId"] - ) - - # 2. update first - # get deployed bucket name - outputs = aws_client.cloudformation.describe_stacks(StackName=stack["StackId"])["Stacks"][ - 0 - ]["Outputs"] - outputs = {k["OutputKey"]: k["OutputValue"] for k in outputs} - bucket_name = outputs["ServerlessDeploymentBucketName"] - - # upload zip file to s3 bucket - # "serverless/test-service/local/1708076358388-2024-02-16T09:39:18.388Z/api.zip" - handler1_filename = os.path.join(os.path.dirname(__file__), "handlers/handler1/api.zip") - aws_client.s3.upload_file( - Filename=handler1_filename, - Bucket=bucket_name, - Key="serverless/test-service/local/1708076358388-2024-02-16T09:39:18.388Z/api.zip", - ) - - template_content = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/serverless-apigw-lambda.update.json", - ) - ) - stack = aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template_content, - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack["StackId"] - ) - - get_fn_1 = aws_client.lambda_.get_function(FunctionName="test-service-local-api") - assert get_fn_1["Configuration"]["Handler"] == "index.handler" - - # # 3. update second - # # upload zip file to s3 bucket - handler2_filename = os.path.join(os.path.dirname(__file__), "handlers/handler2/api.zip") - aws_client.s3.upload_file( - Filename=handler2_filename, - Bucket=bucket_name, - Key="serverless/test-service/local/1708076568092-2024-02-16T09:42:48.092Z/api.zip", - ) - - template_content = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../../templates/serverless-apigw-lambda.update2.json", - ) - ) - stack = aws_client.cloudformation.update_stack( - StackName=stack_name, - TemplateBody=template_content, - Capabilities=["CAPABILITY_NAMED_IAM"], - ) - aws_client.cloudformation.get_waiter("stack_update_complete").wait( - StackName=stack["StackId"] - ) - get_fn_2 = aws_client.lambda_.get_function(FunctionName="test-service-local-api") - assert get_fn_2["Configuration"]["Handler"] == "index.handler2" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.snapshot.json deleted file mode 100644 index bffa8bf5ed3af..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.snapshot.json +++ /dev/null @@ -1,682 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": { - "recorded-date": "15-07-2025, 19:29:28", - "recorded-content": { - "rest_api": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "method": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "GET", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "connectionType": "INTERNET", - "httpMethod": "GET", - "integrationResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'", - "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,POST'", - "method.response.header.Access-Control-Allow-Origin": "'*'" - }, - "statusCode": "200" - } - }, - "passthroughBehavior": "WHEN_NO_MATCH", - "timeoutInMillis": 29000, - "type": "HTTP_PROXY", - "uri": "http://www.example.com" - }, - "methodResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Headers": true, - "method.response.header.Access-Control-Allow-Methods": true, - "method.response.header.Access-Control-Allow-Origin": true - }, - "statusCode": "200" - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": { - "recorded-date": "15-07-2025, 19:29:58", - "recorded-content": { - "rest-api": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "policy": { - "Statement": [ - { - "Action": "*", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Resource": "*", - "Sid": "AllowInvokeAPI" - } - ], - "Version": "2012-10-17" - }, - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "MyApi", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": { - "recorded-date": "15-07-2025, 20:32:03", - "recorded-content": { - "rest-api": { - "apiKeySource": "HEADER", - "binaryMediaTypes": [ - "image/png", - "image/jpg", - "image/gif", - "application/pdf" - ], - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "REGIONAL" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "ApiGatewayRestApi", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "version": "1.0.0", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "resources": { - "items": [ - { - "id": "", - "path": "/" - }, - { - "id": "", - "parentId": "", - "path": "/pets", - "pathPart": "pets", - "resourceMethods": { - "GET": {} - } - }, - { - "id": "", - "parentId": "", - "path": "/pets/{petId}", - "pathPart": "{petId}", - "resourceMethods": { - "GET": {} - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-stage": { - "cacheClusterEnabled": false, - "cacheClusterStatus": "NOT_AVAILABLE", - "createdDate": "datetime", - "deploymentId": "", - "description": "Test Stage 123", - "lastUpdatedDate": "datetime", - "methodSettings": { - "*/*": { - "cacheDataEncrypted": false, - "cacheTtlInSeconds": 300, - "cachingEnabled": false, - "dataTraceEnabled": true, - "loggingLevel": "ERROR", - "metricsEnabled": true, - "requireAuthorizationForCacheControl": true, - "throttlingBurstLimit": 5000, - "throttlingRateLimit": 10000.0, - "unauthorizedCacheControlHeaderStrategy": "SUCCEED_WITH_RESPONSE_HEADER" - } - }, - "stageName": "local", - "tags": { - "aws:cloudformation:logical-id": "ApiGWStage", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack/stack-name/", - "aws:cloudformation:stack-name": "stack-name" - }, - "tracingEnabled": true, - "variables": { - "TestCasing": "myvar", - "testCasingTwo": "myvar2", - "testlowcasing": "myvar3" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": { - "recorded-date": "21-06-2024, 00:09:05", - "recorded-content": { - "get-resources": { - "items": [ - { - "id": "", - "path": "/" - }, - { - "id": "", - "parentId": "", - "path": "/validated", - "pathPart": "validated", - "resourceMethods": { - "ANY": {} - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-models": { - "items": [ - { - "contentType": "application/json", - "description": "This is a default empty schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object" - } - }, - { - "contentType": "application/json", - "description": "This is a default error schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - }, - { - "contentType": "application/json", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "", - "type": "object", - "properties": { - "integer_field": { - "type": "number" - }, - "string_field": { - "type": "string" - } - }, - "required": [ - "string_field", - "integer_field" - ] - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-request-validators": { - "items": [ - { - "id": "", - "name": "", - "validateRequestBody": true, - "validateRequestParameters": false - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-method-any": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "ANY", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "integrationResponses": { - "200": { - "statusCode": "200" - } - }, - "passthroughBehavior": "NEVER", - "requestTemplates": { - "application/json": { - "statusCode": 200 - } - }, - "timeoutInMillis": 29000, - "type": "MOCK" - }, - "methodResponses": { - "200": { - "statusCode": "200" - } - }, - "requestModels": { - "application/json": "" - }, - "requestValidatorId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_with_apigateway_resources": { - "recorded-date": "20-06-2024, 23:54:26", - "recorded-content": { - "get-method-post": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "POST", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "integrationResponses": { - "202": { - "responseTemplates": { - "application/json": { - "operation": "celeste_account_create", - "data": { - "key": "123e4567-e89b-12d3-a456-426614174000", - "secret": "123e4567-e89b-12d3-a456-426614174000" - } - } - }, - "selectionPattern": "2\\d{2}", - "statusCode": "202" - }, - "404": { - "responseTemplates": { - "application/json": { - "message": "Not Found" - } - }, - "selectionPattern": "404", - "statusCode": "404" - }, - "500": { - "responseTemplates": { - "application/json": { - "message": "Unknown " - } - }, - "selectionPattern": "5\\d{2}", - "statusCode": "500" - } - }, - "passthroughBehavior": "WHEN_NO_TEMPLATES", - "requestTemplates": { - "application/json": "" - }, - "timeoutInMillis": 29000, - "type": "MOCK" - }, - "methodResponses": { - "202": { - "responseModels": { - "application/json": "" - }, - "statusCode": "202" - }, - "500": { - "responseModels": { - "application/json": "" - }, - "statusCode": "500" - } - }, - "operationName": "create_account", - "requestParameters": { - "method.request.path.account": true - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-models": { - "items": [ - { - "contentType": "application/json", - "description": "This is a default empty schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object" - } - }, - { - "contentType": "application/json", - "description": "This is a default error schema model", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": " Schema", - "type": "object", - "properties": { - "message": { - "type": "string" - } - } - } - }, - { - "contentType": "application/json", - "id": "", - "name": "", - "schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "AccountCreate", - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "email": { - "format": "email", - "type": "string" - } - } - } - }, - { - "contentType": "application/json", - "id": "", - "name": "", - "schema": {} - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": { - "recorded-date": "06-07-2023, 21:01:08", - "recorded-content": { - "get-resources": { - "items": [ - { - "id": "", - "path": "/", - "resourceMethods": { - "GET": {}, - "OPTIONS": {} - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-authorizers": { - "items": [ - { - "authType": "custom", - "authorizerUri": "", - "id": "", - "identitySource": "method.request.header.Authorization", - "name": "", - "type": "TOKEN" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-method-GET": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "GET", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "httpMethod": "POST", - "passthroughBehavior": "WHEN_NO_MATCH", - "timeoutInMillis": 29000, - "type": "AWS_PROXY", - "uri": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get-method-OPTIONS": { - "apiKeyRequired": false, - "authorizationType": "NONE", - "httpMethod": "OPTIONS", - "methodIntegration": { - "cacheKeyParameters": [], - "cacheNamespace": "", - "integrationResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Credentials": "'true'", - "method.response.header.Access-Control-Allow-Headers": "'Content-Type,Authorization,x-test-header'", - "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,POST,GET,PUT'", - "method.response.header.Access-Control-Allow-Origin": "'http://localhost:8000'" - }, - "responseTemplates": { - "application/json": {} - }, - "statusCode": "200" - } - }, - "passthroughBehavior": "WHEN_NO_MATCH", - "requestTemplates": { - "application/json": { - "statusCode": 200 - } - }, - "timeoutInMillis": 29000, - "type": "MOCK" - }, - "methodResponses": { - "200": { - "responseParameters": { - "method.response.header.Access-Control-Allow-Credentials": false, - "method.response.header.Access-Control-Allow-Headers": false, - "method.response.header.Access-Control-Allow-Methods": false, - "method.response.header.Access-Control-Allow-Origin": false - }, - "statusCode": "200" - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_usage_plan": { - "recorded-date": "13-09-2024, 09:57:21", - "recorded-content": { - "usage-plan": { - "apiStages": [ - { - "apiId": "", - "stage": "" - } - ], - "id": "", - "name": "", - "quota": { - "limit": 5000, - "offset": 0, - "period": "MONTH" - }, - "tags": { - "aws:cloudformation:logical-id": "UsagePlan", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "test": "value1", - "test2": "hardcoded" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "updated-usage-plan": { - "apiStages": [ - { - "apiId": "", - "stage": "" - } - ], - "id": "", - "name": "", - "quota": { - "limit": 7000, - "offset": 0, - "period": "MONTH" - }, - "tags": { - "aws:cloudformation:logical-id": "UsagePlan", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "test": "value-updated", - "test2": "hardcoded" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_apigateway_stage": { - "recorded-date": "07-11-2024, 05:35:20", - "recorded-content": { - "created-stage": { - "cacheClusterEnabled": false, - "cacheClusterStatus": "NOT_AVAILABLE", - "createdDate": "datetime", - "deploymentId": "", - "lastUpdatedDate": "datetime", - "methodSettings": {}, - "stageName": "dev", - "tags": { - "aws:cloudformation:logical-id": "Stage", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "tracingEnabled": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "updated-stage": { - "cacheClusterEnabled": false, - "cacheClusterStatus": "NOT_AVAILABLE", - "createdDate": "datetime", - "deploymentId": "", - "lastUpdatedDate": "datetime", - "methodSettings": {}, - "stageName": "dev", - "tags": { - "aws:cloudformation:logical-id": "Stage", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "tracingEnabled": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.validation.json deleted file mode 100644 index 43ad31fc92767..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.validation.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::TestServerlessApigwLambda::test_serverless_like_deployment_with_update": { - "last_validated_date": "2024-02-19T08:55:12+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_api_gateway_with_policy_as_dict": { - "last_validated_date": "2025-07-15T19:30:16+00:00", - "durations_in_seconds": { - "setup": 0.5, - "call": 11.81, - "teardown": 17.53, - "total": 29.84 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_apigateway_rest_api": { - "last_validated_date": "2024-06-25T18:12:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_from_s3_swagger": { - "last_validated_date": "2025-07-16T00:25:05+00:00", - "durations_in_seconds": { - "setup": 1.15, - "call": 18.86, - "teardown": 8.08, - "total": 28.09 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_integration": { - "last_validated_date": "2025-07-15T19:29:44+00:00", - "durations_in_seconds": { - "setup": 0.57, - "call": 26.97, - "teardown": 15.37, - "total": 42.91 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_deploy_apigateway_models": { - "last_validated_date": "2024-06-21T00:09:05+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_cfn_with_apigateway_resources": { - "last_validated_date": "2024-06-20T23:54:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_rest_api_serverless_ref_resolving": { - "last_validated_date": "2023-07-06T19:01:08+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_apigateway_stage": { - "last_validated_date": "2024-11-07T05:35:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_apigateway.py::test_update_usage_plan": { - "last_validated_date": "2024-09-13T09:57:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py deleted file mode 100644 index 0140153b6dc89..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py +++ /dev/null @@ -1,145 +0,0 @@ -import os - -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -class TestCdkInit: - @pytest.mark.parametrize("bootstrap_version", ["10", "11", "12"]) - @markers.aws.validated - def test_cdk_bootstrap(self, deploy_cfn_template, bootstrap_version, aws_client): - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - f"../../../../../templates/cdk_bootstrap_v{bootstrap_version}.yaml", - ), - parameters={"FileAssetsBucketName": f"cdk-bootstrap-{short_uid()}"}, - ) - init_stack_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cdk_init_template.yaml" - ) - ) - assert init_stack_result.outputs["BootstrapVersionOutput"] == bootstrap_version - stack_res = aws_client.cloudformation.describe_stack_resources( - StackName=init_stack_result.stack_id, LogicalResourceId="CDKMetadata" - ) - assert len(stack_res["StackResources"]) == 1 - assert stack_res["StackResources"][0]["LogicalResourceId"] == "CDKMetadata" - - @pytest.mark.skip(reason="CFNV2:Provider") - @markers.aws.validated - def test_cdk_bootstrap_redeploy(self, aws_client, cleanup_stacks, cleanup_changesets, cleanups): - """Test that simulates a sequence of commands executed by CDK when running 'cdk bootstrap' twice""" - - stack_name = f"CDKToolkit-{short_uid()}" - change_set_name = f"cdk-deploy-change-set-{short_uid()}" - - def clean_resources(): - cleanup_stacks([stack_name]) - cleanup_changesets([change_set_name]) - - cleanups.append(clean_resources) - - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/cdk_bootstrap.yml") - ) - aws_client.cloudformation.create_change_set( - StackName=stack_name, - ChangeSetName=change_set_name, - TemplateBody=template_body, - ChangeSetType="CREATE", - Capabilities=["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"], - Description="CDK Changeset for execution 731ed7da-8b2d-49c6-bca3-4698b6875954", - Parameters=[ - { - "ParameterKey": "BootstrapVariant", - "ParameterValue": "AWS CDK: Default Resources", - }, - {"ParameterKey": "TrustedAccounts", "ParameterValue": ""}, - {"ParameterKey": "TrustedAccountsForLookup", "ParameterValue": ""}, - {"ParameterKey": "CloudFormationExecutionPolicies", "ParameterValue": ""}, - {"ParameterKey": "FileAssetsBucketKmsKeyId", "ParameterValue": "AWS_MANAGED_KEY"}, - {"ParameterKey": "PublicAccessBlockConfiguration", "ParameterValue": "true"}, - {"ParameterKey": "Qualifier", "ParameterValue": "hnb659fds"}, - {"ParameterKey": "UseExamplePermissionsBoundary", "ParameterValue": "false"}, - ], - ) - aws_client.cloudformation.describe_change_set( - StackName=stack_name, ChangeSetName=change_set_name - ) - - aws_client.cloudformation.get_waiter("change_set_create_complete").wait( - StackName=stack_name, ChangeSetName=change_set_name - ) - - aws_client.cloudformation.execute_change_set( - StackName=stack_name, ChangeSetName=change_set_name - ) - - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - aws_client.cloudformation.describe_stacks(StackName=stack_name) - - # When CDK toolstrap command is executed again it just confirms that the template is the same - aws_client.sts.get_caller_identity() - aws_client.cloudformation.get_template(StackName=stack_name, TemplateStage="Original") - - # TODO: create scenario where the template is different to catch cdk behavior - - -class TestCdkSampleApp: - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.Policy.Statement..Condition", - "$..Attributes.Policy.Statement..Resource", - "$..StackResourceSummaries..PhysicalResourceId", - ] - ) - @markers.aws.validated - def test_cdk_sample(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.sqs_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - snapshot.add_transformer( - SortingTransformer("StackResourceSummaries", lambda x: x["LogicalResourceId"]), - priority=-1, - ) - - deploy = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cdk_sample_app.yaml" - ), - max_wait=120, - ) - - queue_url = deploy.outputs["QueueUrl"] - - queue_attr_policy = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url, AttributeNames=["Policy"] - ) - snapshot.match("queue_attr_policy", queue_attr_policy) - stack_resources = aws_client.cloudformation.list_stack_resources(StackName=deploy.stack_id) - snapshot.match("stack_resources", stack_resources) - - # physical resource id of the queue policy AWS::SQS::QueuePolicy - queue_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deploy.stack_id, LogicalResourceId="CdksampleQueuePolicyFA91005A" - ) - snapshot.add_transformer( - snapshot.transform.regex( - queue_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - # TODO: make sure phys id of the resource conforms to this format: stack-d98dcad5-CdksampleQueuePolicyFA91005A-1WYVV4PMCWOYI diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.snapshot.json deleted file mode 100644 index 2068d98220c4a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.snapshot.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": { - "recorded-date": "04-11-2022, 15:15:44", - "recorded-content": { - "queue_attr_policy": { - "Attributes": { - "Policy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com" - }, - "Action": "sqs:SendMessage", - "Resource": "arn::sqs::111111111111:", - "Condition": { - "ArnEquals": { - "aws:SourceArn": "arn::sns::111111111111:" - } - } - } - ] - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResourceSummaries": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleQueue3139C8CD", - "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SQS::Queue" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleQueueCdksampleStackCdksampleTopicCB3FDFDDC0BCF47C", - "PhysicalResourceId": "arn::sns::111111111111::", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Subscription" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleQueuePolicyFA91005A", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SQS::QueuePolicy" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "CdksampleTopic7AD235A4", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Topic" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.validation.json deleted file mode 100644 index b627e80340018..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cdk.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[10]": { - "last_validated_date": "2024-06-25T18:37:34+00:00" - }, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[11]": { - "last_validated_date": "2024-06-25T18:40:57+00:00" - }, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkInit::test_cdk_bootstrap[12]": { - "last_validated_date": "2024-06-25T18:44:21+00:00" - }, - "tests/aws/services/cloudformation/resources/test_cdk.py::TestCdkSampleApp::test_cdk_sample": { - "last_validated_date": "2022-11-04T14:15:44+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py deleted file mode 100644 index 65f79e38e23a2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py +++ /dev/null @@ -1,137 +0,0 @@ -import logging -import os -import textwrap -import time -import uuid -from threading import Thread -from typing import TYPE_CHECKING - -import pytest -import requests - -from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -if TYPE_CHECKING: - try: - from mypy_boto3_ssm import SSMClient - except ImportError: - pass - -LOG = logging.getLogger(__name__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -PARAMETER_NAME = "wait-handle-url" - - -class SignalSuccess(Thread): - def __init__(self, client: "SSMClient"): - Thread.__init__(self) - self.client = client - self.session = requests.Session() - self.should_break = False - - def run(self): - while not self.should_break: - try: - LOG.debug("fetching parameter") - res = self.client.get_parameter(Name=PARAMETER_NAME) - url = res["Parameter"]["Value"] - LOG.info("signalling url %s", url) - - payload = { - "Status": "SUCCESS", - "Reason": "Wait condition reached", - "UniqueId": str(uuid.uuid4()), - "Data": "Application has completed configuration.", - } - r = self.session.put(url, json=payload) - LOG.debug("status from signalling: %s", r.status_code) - r.raise_for_status() - LOG.debug("status signalled") - break - except self.client.exceptions.ParameterNotFound: - LOG.warning("parameter not available, trying again") - time.sleep(5) - except Exception: - LOG.exception("got python exception") - raise - - def stop(self): - self.should_break = True - - -@markers.snapshot.skip_snapshot_verify(paths=["$..WaitConditionName"]) -@markers.aws.validated -def test_waitcondition(deploy_cfn_template, snapshot, aws_client): - """ - Complicated test, since we have a wait condition that must signal - a successful value to before the stack finishes. We use the - fact that CFn will deploy the SSM parameter before moving on - to the wait condition itself, so in a background thread we - try to set the value to success so that the stack will - deploy correctly. - """ - signal_thread = SignalSuccess(aws_client.ssm) - signal_thread.daemon = True - signal_thread.start() - - try: - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_waitcondition.yaml" - ), - parameters={"ParameterName": PARAMETER_NAME}, - ) - finally: - signal_thread.stop() - - wait_handle_id = stack.outputs["WaitHandleId"] - wait_condition_name = stack.outputs["WaitConditionRef"] - - # TODO: more stringent tests - assert wait_handle_id is not None - # snapshot.match("waithandle_ref", wait_handle_id) - snapshot.match("waitcondition_ref", {"WaitConditionName": wait_condition_name}) - - -@markers.aws.validated -def test_create_macro(deploy_cfn_template, create_lambda_function, snapshot, aws_client): - macro_name = f"macro-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(macro_name, "")) - - function_name = f"macro_lambda_{short_uid()}" - - handler_code = textwrap.dedent( - """ - def handler(event, context): - pass - """ - ) - - create_lambda_function( - func_name=function_name, - handler_file=handler_code, - runtime=Runtime.python3_12, - ) - - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/macro_resource.yml" - ) - assert os.path.isfile(template_path) - stack = deploy_cfn_template( - template_path=template_path, - parameters={ - "FunctionName": function_name, - "MacroName": macro_name, - }, - ) - - snapshot.match("stack-outputs", stack.outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.snapshot.json deleted file mode 100644 index 3c607af7f69ec..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.snapshot.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitconditionhandle": { - "recorded-date": "17-05-2023, 15:55:08", - "recorded-content": { - "waithandle_ref": "https://cloudformation-waitcondition-.s3..amazonaws.com/arn%3Aaws%3Acloudformation%3A%3A111111111111%3Astack/stack-03ad7786/c7b3de40-f4c2-11ed-b84b-0a57ddc705d2/WaitHandle?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230517T145504Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86399&X-Amz-Credential=AKIAYYGVRKE7CKDBHLUS%2F20230517%2F%2Fs3%2Faws4_request&X-Amz-Signature=3c79384f6647bd2c655ac78e6811ea0fff9b3a52a9bd751005d35f2a04f6533c" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitcondition": { - "recorded-date": "18-05-2023, 11:09:21", - "recorded-content": { - "waitcondition_ref": { - "WaitConditionName": "arn::cloudformation::111111111111:stack/stack-6cc1b50e/f9764ac0-f563-11ed-82f7-061d4a7b8a1e/WaitHandle" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_create_macro": { - "recorded-date": "09-06-2023, 14:30:11", - "recorded-content": { - "stack-outputs": { - "MacroRef": "" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.validation.json deleted file mode 100644 index 0aeaeefb84d2e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_create_macro": { - "last_validated_date": "2023-06-09T12:30:11+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudformation.py::test_waitcondition": { - "last_validated_date": "2023-05-18T09:09:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py deleted file mode 100644 index d1acf12c8a064..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py +++ /dev/null @@ -1,118 +0,0 @@ -import json -import os -import re - -import pytest -from localstack_snapshot.snapshots.transformer import KeyValueBasedTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.snapshots.transformer_utility import PATTERN_ARN -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_alarm_creation(deploy_cfn_template, snapshot): - snapshot.add_transformer(snapshot.transform.resource_name()) - alarm_name = f"alarm-{short_uid()}" - - template = json.dumps( - { - "Resources": { - "Alarm": { - "Type": "AWS::CloudWatch::Alarm", - "Properties": { - "AlarmName": alarm_name, - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "EvaluationPeriods": 1, - "MetricName": "Errors", - "Namespace": "AWS/Lambda", - "Period": 300, - "Statistic": "Average", - "Threshold": 1, - }, - } - }, - "Outputs": { - "AlarmName": {"Value": {"Ref": "Alarm"}}, - "AlarmArnFromAtt": {"Value": {"Fn::GetAtt": "Alarm.Arn"}}, - }, - } - ) - - outputs = deploy_cfn_template(template=template).outputs - snapshot.match("alarm_outputs", outputs) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..StateReason", - "$..StateReasonData", - "$..StateValue", - ] -) -def test_composite_alarm_creation(aws_client, deploy_cfn_template, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("Region", "region-name-full")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cw_composite_alarm.yml" - ), - ) - composite_alarm_name = stack.outputs["CompositeAlarmName"] - - def alarm_action_name_transformer(key: str, val: str): - if key == "AlarmActions" and isinstance(val, list) and len(val) == 1: - # we expect only one item in the list - value = val[0] - match = re.match(PATTERN_ARN, value) - if match: - res = match.groups()[-1] - if ":" in res: - return res.split(":")[-1] - return res - return None - - snapshot.add_transformer( - KeyValueBasedTransformer(alarm_action_name_transformer, "alarm-action-name"), - ) - response = aws_client.cloudwatch.describe_alarms( - AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] - ) - snapshot.match("composite_alarm", response["CompositeAlarms"]) - - metric_alarm_name = stack.outputs["MetricAlarmName"] - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[metric_alarm_name]) - snapshot.match("metric_alarm", response["MetricAlarms"]) - - stack.destroy() - response = aws_client.cloudwatch.describe_alarms( - AlarmNames=[composite_alarm_name], AlarmTypes=["CompositeAlarm"] - ) - assert not response["CompositeAlarms"] - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[metric_alarm_name]) - assert not response["MetricAlarms"] - - -@markers.aws.validated -def test_alarm_ext_statistic(aws_client, deploy_cfn_template, snapshot): - snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_cw_simple_alarm.yml" - ), - ) - alarm_name = stack.outputs["MetricAlarmName"] - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) - snapshot.match("simple_alarm", response["MetricAlarms"]) - - stack.destroy() - response = aws_client.cloudwatch.describe_alarms(AlarmNames=[alarm_name]) - assert not response["MetricAlarms"] diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.snapshot.json deleted file mode 100644 index 171d60de6e8ac..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.snapshot.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_creation": { - "recorded-date": "25-09-2023, 10:28:42", - "recorded-content": { - "alarm_outputs": { - "AlarmArnFromAtt": "arn::cloudwatch::111111111111:alarm:", - "AlarmName": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_composite_alarm_creation": { - "recorded-date": "16-07-2024, 10:41:22", - "recorded-content": { - "composite_alarm": [ - { - "ActionsEnabled": true, - "AlarmActions": [ - "arn::sns::111111111111:" - ], - "AlarmArn": "arn::cloudwatch::111111111111:alarm:HighResourceUsage", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "AlarmDescription": "Indicates that the system resource usage is high while no known deployment is in progress", - "AlarmName": "HighResourceUsage", - "AlarmRule": "(ALARM(HighCPUUsage) OR ALARM(HighMemoryUsage))", - "InsufficientDataActions": [], - "OKActions": [], - "StateReason": "arn::cloudwatch::111111111111:alarm:HighResourceUsage was created and its alarm rule evaluates to OK", - "StateReasonData": { - "triggeringAlarms": [ - { - "arn": "arn::cloudwatch::111111111111:alarm:HighCPUUsage", - "state": { - "value": "INSUFFICIENT_DATA", - "timestamp": "date" - } - }, - { - "arn": "arn::cloudwatch::111111111111:alarm:HighMemoryUsage", - "state": { - "value": "INSUFFICIENT_DATA", - "timestamp": "date" - } - } - ] - }, - "StateUpdatedTimestamp": "timestamp", - "StateValue": "OK", - "StateTransitionedTimestamp": "timestamp" - } - ], - "metric_alarm": [ - { - "AlarmName": "HighMemoryUsage", - "AlarmArn": "arn::cloudwatch::111111111111:alarm:HighMemoryUsage", - "AlarmDescription": "Memory usage is high", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "ActionsEnabled": true, - "OKActions": [], - "AlarmActions": [], - "InsufficientDataActions": [], - "StateValue": "INSUFFICIENT_DATA", - "StateReason": "Unchecked: Initial alarm creation", - "StateUpdatedTimestamp": "timestamp", - "MetricName": "MemoryUsage", - "Namespace": "CustomNamespace", - "Statistic": "Average", - "Dimensions": [], - "Period": 60, - "EvaluationPeriods": 1, - "Threshold": 65.0, - "ComparisonOperator": "GreaterThanThreshold", - "TreatMissingData": "breaching", - "StateTransitionedTimestamp": "timestamp" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_no_statistic": { - "recorded-date": "27-11-2023, 10:08:09", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_ext_statistic": { - "recorded-date": "27-11-2023, 10:09:46", - "recorded-content": { - "simple_alarm": [ - { - "AlarmName": "", - "AlarmArn": "arn::cloudwatch::111111111111:alarm:", - "AlarmDescription": "uses extended statistic", - "AlarmConfigurationUpdatedTimestamp": "timestamp", - "ActionsEnabled": true, - "OKActions": [], - "AlarmActions": [], - "InsufficientDataActions": [], - "StateValue": "INSUFFICIENT_DATA", - "StateReason": "Unchecked: Initial alarm creation", - "StateUpdatedTimestamp": "timestamp", - "MetricName": "Duration", - "Namespace": "", - "ExtendedStatistic": "p99", - "Dimensions": [ - { - "Name": "FunctionName", - "Value": "my-function" - } - ], - "Period": 300, - "Unit": "Count", - "EvaluationPeriods": 3, - "DatapointsToAlarm": 3, - "Threshold": 10.0, - "ComparisonOperator": "GreaterThanOrEqualToThreshold", - "TreatMissingData": "ignore", - "StateTransitionedTimestamp": "timestamp" - } - ] - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.validation.json deleted file mode 100644 index 9888ffd954a05..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_creation": { - "last_validated_date": "2023-09-25T08:28:42+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_alarm_ext_statistic": { - "last_validated_date": "2023-11-27T09:09:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_cloudwatch.py::test_composite_alarm_creation": { - "last_validated_date": "2024-07-16T10:43:30+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py deleted file mode 100644 index 4a0b900772ef6..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py +++ /dev/null @@ -1,217 +0,0 @@ -import os - -import aws_cdk as cdk -import pytest -from aws_cdk import aws_dynamodb as dynamodb -from aws_cdk.aws_dynamodb import BillingMode - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.aws.arns import get_partition -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_deploy_stack_with_dynamodb_table(deploy_cfn_template, aws_client, region_name): - env = "Staging" - ddb_table_name_prefix = f"ddb-table-{short_uid()}" - ddb_table_name = f"{ddb_table_name_prefix}-{env}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/deploy_template_3.yaml" - ), - parameters={"tableName": ddb_table_name_prefix, "env": env}, - ) - - assert stack.outputs["Arn"].startswith(f"arn:{get_partition(region_name)}:dynamodb") - assert f"table/{ddb_table_name}" in stack.outputs["Arn"] - assert stack.outputs["Name"] == ddb_table_name - - rs = aws_client.dynamodb.list_tables() - assert ddb_table_name in rs["TableNames"] - - stack.destroy() - rs = aws_client.dynamodb.list_tables() - assert ddb_table_name not in rs["TableNames"] - - -@markers.aws.validated -def test_globalindex_read_write_provisioned_throughput_dynamodb_table( - deploy_cfn_template, aws_client -): - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/deploy_template_3.yaml" - ), - parameters={"tableName": "dynamodb", "env": "test"}, - ) - - response = aws_client.dynamodb.describe_table(TableName="dynamodb-test") - - if response["Table"]["ProvisionedThroughput"]: - throughput = response["Table"]["ProvisionedThroughput"] - assert isinstance(throughput["ReadCapacityUnits"], int) - assert isinstance(throughput["WriteCapacityUnits"], int) - - for global_index in response["Table"]["GlobalSecondaryIndexes"]: - index_provisioned = global_index["ProvisionedThroughput"] - test_read_capacity = index_provisioned["ReadCapacityUnits"] - test_write_capacity = index_provisioned["WriteCapacityUnits"] - assert isinstance(test_read_capacity, int) - assert isinstance(test_write_capacity, int) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.DeletionProtectionEnabled", - ] -) -def test_default_name_for_table(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_table_defaults.yml" - ), - ) - - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - list_tags = aws_client.dynamodb.list_tags_of_resource(ResourceArn=stack.outputs["TableArn"]) - snapshot.match("list_tags_of_resource", list_tags) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - "$..Table.DeletionProtectionEnabled", - ] -) -@pytest.mark.parametrize("billing_mode", ["PROVISIONED", "PAY_PER_REQUEST"]) -def test_billing_mode_as_conditional(deploy_cfn_template, snapshot, aws_client, billing_mode): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - snapshot.add_transformer( - snapshot.transform.key_value("LatestStreamLabel", "latest-stream-label") - ) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_billing_conditional.yml" - ), - parameters={"BillingModeParameter": billing_mode}, - ) - - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Table.DeletionProtectionEnabled", - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - ] -) -def test_global_table(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_global_table.yml" - ), - ) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - stack.destroy() - - with pytest.raises(Exception) as ex: - aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - - error_code = ex.value.response["Error"]["Code"] - assert "ResourceNotFoundException" == error_code - - -@markers.aws.validated -def test_ttl_cdk(aws_client, snapshot, infrastructure_setup): - infra = infrastructure_setup(namespace="DDBTableTTL") - stack = cdk.Stack(infra.cdk_app, "DDBStackTTL") - - table = dynamodb.Table( - stack, - id="Table", - billing_mode=BillingMode.PAY_PER_REQUEST, - partition_key=dynamodb.Attribute(name="id", type=dynamodb.AttributeType.STRING), - removal_policy=cdk.RemovalPolicy.RETAIN, - time_to_live_attribute="expire_at", - ) - - cdk.CfnOutput(stack, "TableName", value=table.table_name) - - with infra.provisioner() as prov: - outputs = prov.get_stack_outputs(stack_name="DDBStackTTL") - table_name = outputs["TableName"] - table = aws_client.dynamodb.describe_time_to_live(TableName=table_name) - snapshot.match("table", table) - - -@markers.aws.validated -# We return field bellow, while AWS doesn't return them -@markers.snapshot.skip_snapshot_verify( - [ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - ] -) -def test_table_with_ttl_and_sse(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/dynamodb_table_sse_enabled.yml" - ), - ) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - snapshot.add_transformer(snapshot.transform.key_value("KMSMasterKeyArn", "kms-arn")) - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) - - -@markers.aws.validated -# We return the fields bellow, while AWS doesn't return them -@markers.snapshot.skip_snapshot_verify( - [ - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - ] -) -def test_global_table_with_ttl_and_sse(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/dynamodb_global_table_sse_enabled.yml", - ), - ) - snapshot.add_transformer(snapshot.transform.key_value("TableName", "table-name")) - snapshot.add_transformer(snapshot.transform.key_value("KMSMasterKeyArn", "kms-arn")) - - response = aws_client.dynamodb.describe_table(TableName=stack.outputs["TableName"]) - snapshot.match("table_description", response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.snapshot.json deleted file mode 100644 index 88af39a8953e1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.snapshot.json +++ /dev/null @@ -1,349 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_default_name_for_table": { - "recorded-date": "28-08-2023, 12:34:19", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "keyName", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "keyName", - "KeyType": "HASH" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags_of_resource": { - "Tags": [ - { - "Key": "TagKey1", - "Value": "TagValue1" - }, - { - "Key": "TagKey2", - "Value": "TagValue2" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": { - "recorded-date": "28-08-2023, 12:34:41", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "LatestStreamArn": "arn::dynamodb::111111111111:table//stream/", - "LatestStreamLabel": "", - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, - "StreamSpecification": { - "StreamEnabled": true, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": { - "recorded-date": "28-08-2023, 12:35:02", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "BillingModeSummary": { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "datetime" - }, - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "LatestStreamArn": "arn::dynamodb::111111111111:table//stream/", - "LatestStreamLabel": "", - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - }, - "StreamSpecification": { - "StreamEnabled": true, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table": { - "recorded-date": "01-12-2023, 12:54:13", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "keyName", - "AttributeType": "S" - } - ], - "BillingModeSummary": { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "datetime" - }, - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "keyName", - "KeyType": "HASH" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_ttl_cdk": { - "recorded-date": "14-02-2024, 13:29:07", - "recorded-content": { - "table": { - "TimeToLiveDescription": { - "AttributeName": "expire_at", - "TimeToLiveStatus": "ENABLED" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_table_with_ttl_and_sse": { - "recorded-date": "12-03-2024, 15:42:18", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "pk", - "AttributeType": "S" - }, - { - "AttributeName": "sk", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "pk", - "KeyType": "HASH" - }, - { - "AttributeName": "sk", - "KeyType": "RANGE" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 1, - "WriteCapacityUnits": 1 - }, - "SSEDescription": { - "KMSMasterKeyArn": "", - "SSEType": "KMS", - "Status": "ENABLED" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": { - "recorded-date": "12-03-2024, 15:44:36", - "recorded-content": { - "table_description": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "gsi1pk", - "AttributeType": "S" - }, - { - "AttributeName": "gsi1sk", - "AttributeType": "S" - }, - { - "AttributeName": "pk", - "AttributeType": "S" - }, - { - "AttributeName": "sk", - "AttributeType": "S" - } - ], - "BillingModeSummary": { - "BillingMode": "PAY_PER_REQUEST", - "LastUpdateToPayPerRequestDateTime": "datetime" - }, - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "GlobalSecondaryIndexes": [ - { - "IndexArn": "arn::dynamodb::111111111111:table//index/GSI1", - "IndexName": "GSI1", - "IndexSizeBytes": 0, - "IndexStatus": "ACTIVE", - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "gsi1pk", - "KeyType": "HASH" - }, - { - "AttributeName": "gsi1sk", - "KeyType": "RANGE" - } - ], - "Projection": { - "ProjectionType": "ALL" - }, - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - } - } - ], - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "pk", - "KeyType": "HASH" - }, - { - "AttributeName": "sk", - "KeyType": "RANGE" - } - ], - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 0, - "WriteCapacityUnits": 0 - }, - "SSEDescription": { - "KMSMasterKeyArn": "", - "SSEType": "KMS", - "Status": "ENABLED" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableClassSummary": { - "TableClass": "STANDARD" - }, - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.validation.json deleted file mode 100644 index a93ac64a42317..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PAY_PER_REQUEST]": { - "last_validated_date": "2023-08-28T10:35:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_billing_mode_as_conditional[PROVISIONED]": { - "last_validated_date": "2023-08-28T10:34:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_default_name_for_table": { - "last_validated_date": "2023-08-28T10:34:19+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table": { - "last_validated_date": "2023-12-01T11:54:13+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_global_table_with_ttl_and_sse": { - "last_validated_date": "2024-03-12T15:44:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_table_with_ttl_and_sse": { - "last_validated_date": "2024-03-12T15:42:18+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_dynamodb.py::test_ttl_cdk": { - "last_validated_date": "2024-02-14T13:29:07+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py deleted file mode 100644 index 8cecb1c3627a0..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py +++ /dev/null @@ -1,375 +0,0 @@ -import os - -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - -THIS_FOLDER = os.path.dirname(__file__) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..PropagatingVgws"]) -def test_simple_route_table_creation_without_vpc(deploy_cfn_template, aws_client, snapshot): - ec2 = aws_client.ec2 - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_FOLDER, "../../../../../templates/ec2_route_table_isolated.yaml" - ), - ) - - route_table_id = stack.outputs["RouteTableId"] - route_table = ec2.describe_route_tables(RouteTableIds=[route_table_id])["RouteTables"][0] - - tags = route_table.pop("Tags") - tags_dict = {tag["Key"]: tag["Value"] for tag in tags if "aws:cloudformation" not in tag["Key"]} - snapshot.match("tags", tags_dict) - - snapshot.match("route_table", route_table) - snapshot.add_transformer(snapshot.transform.key_value("VpcId", "vpc-id")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId", "vpc-id")) - - stack.destroy() - with pytest.raises(ec2.exceptions.ClientError): - ec2.describe_route_tables(RouteTableIds=[route_table_id]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..PropagatingVgws"]) -def test_simple_route_table_creation(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_FOLDER, "../../../../../templates/ec2_route_table_simple.yaml" - ) - ) - - route_table_id = stack.outputs["RouteTableId"] - ec2 = aws_client.ec2 - route_table = ec2.describe_route_tables(RouteTableIds=[route_table_id])["RouteTables"][0] - - tags = route_table.pop("Tags") - tags_dict = {tag["Key"]: tag["Value"] for tag in tags if "aws:cloudformation" not in tag["Key"]} - snapshot.match("tags", tags_dict) - - snapshot.match("route_table", route_table) - snapshot.add_transformer(snapshot.transform.key_value("VpcId", "vpc-id")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId", "vpc-id")) - - stack.destroy() - with pytest.raises(ec2.exceptions.ClientError): - ec2.describe_route_tables(RouteTableIds=[route_table_id]) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_vpc_creates_default_sg(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/ec2_vpc_default_sg.yaml") - ) - - vpc_id = result.outputs.get("VpcId") - default_sg = result.outputs.get("VpcDefaultSG") - default_acl = result.outputs.get("VpcDefaultAcl") - - assert vpc_id - assert default_sg - assert default_acl - - security_groups = aws_client.ec2.describe_security_groups(GroupIds=[default_sg])[ - "SecurityGroups" - ] - assert security_groups[0]["VpcId"] == vpc_id - - acls = aws_client.ec2.describe_network_acls(NetworkAclIds=[default_acl])["NetworkAcls"] - assert acls[0]["VpcId"] == vpc_id - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_cfn_with_multiple_route_tables(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/template36.yaml"), - max_wait=180, - ) - vpc_id = result.outputs["VPC"] - - resp = aws_client.ec2.describe_route_tables(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]) - - # 4 route tables being created (validated against AWS): 3 in template + 1 default = 4 - assert len(resp["RouteTables"]) == 4 - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..PropagatingVgws", "$..Tags", "$..Tags..Key", "$..Tags..Value"] -) -def test_cfn_with_multiple_route_table_associations(deploy_cfn_template, aws_client, snapshot): - # TODO: stack does not deploy to AWS - stack = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/template37.yaml") - ) - route_table_id = stack.outputs["RouteTable"] - route_table = aws_client.ec2.describe_route_tables( - Filters=[{"Name": "route-table-id", "Values": [route_table_id]}] - )["RouteTables"][0] - - snapshot.match("route_table", route_table) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableAssociationId")) - snapshot.add_transformer(snapshot.transform.key_value("SubnetId")) - snapshot.add_transformer(snapshot.transform.key_value("VpcId")) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..DriftInformation", "$..Metadata"]) -def test_internet_gateway_ref_and_attr(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/internet_gateway.yml") - ) - - response = aws_client.cloudformation.describe_stack_resource( - StackName=stack.stack_name, LogicalResourceId="Gateway" - ) - - snapshot.add_transformer(snapshot.transform.key_value("RefAttachment", "internet-gateway-ref")) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - snapshot.match("outputs", stack.outputs) - snapshot.match("description", response["StackResourceDetail"]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..OwnerId"]) -def test_dhcp_options(aws_client, deploy_cfn_template, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join(THIS_FOLDER, "../../../../../templates/dhcp_options.yml") - ) - - response = aws_client.ec2.describe_dhcp_options( - DhcpOptionsIds=[stack.outputs["RefDhcpOptions"]] - ) - snapshot.add_transformer(snapshot.transform.key_value("DhcpOptionsId", "dhcp-options-id")) - snapshot.add_transformer(SortingTransformer("DhcpConfigurations", lambda x: x["Key"])) - snapshot.match("description", response["DhcpOptions"][0]) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Tags", - "$..Options.AssociationDefaultRouteTableId", - "$..Options.PropagationDefaultRouteTableId", - "$..Options.TransitGatewayCidrBlocks", # an empty list returned by Moto but not by AWS - "$..Options.SecurityGroupReferencingSupport", # not supported by Moto - ] -) -def test_transit_gateway_attachment(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - THIS_FOLDER, "../../../../../templates/transit_gateway_attachment.yml" - ) - ) - - gateway_description = aws_client.ec2.describe_transit_gateways( - TransitGatewayIds=[stack.outputs["TransitGateway"]] - ) - attachment_description = aws_client.ec2.describe_transit_gateway_attachments( - TransitGatewayAttachmentIds=[stack.outputs["Attachment"]] - ) - - snapshot.add_transformer(snapshot.transform.key_value("TransitGatewayRouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("AssociationDefaultRouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("PropagatioDefaultRouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("ResourceId")) - snapshot.add_transformer(snapshot.transform.key_value("TransitGatewayAttachmentId")) - snapshot.add_transformer(snapshot.transform.key_value("TransitGatewayId")) - - snapshot.match("attachment", attachment_description["TransitGatewayAttachments"][0]) - snapshot.match("gateway", gateway_description["TransitGateways"][0]) - - stack.destroy() - - descriptions = aws_client.ec2.describe_transit_gateways( - TransitGatewayIds=[stack.outputs["TransitGateway"]] - ) - if is_aws_cloud(): - # aws changes the state to deleted - descriptions = descriptions["TransitGateways"][0] - assert descriptions["State"] == "deleted" - else: - # moto directly deletes the transit gateway - transit_gateways_ids = [ - tgateway["TransitGatewayId"] for tgateway in descriptions["TransitGateways"] - ] - assert stack.outputs["TransitGateway"] not in transit_gateways_ids - - attachment_description = aws_client.ec2.describe_transit_gateway_attachments( - TransitGatewayAttachmentIds=[stack.outputs["Attachment"]] - )["TransitGatewayAttachments"] - assert attachment_description[0]["State"] == "deleted" - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..RouteTables..PropagatingVgws", "$..RouteTables..Tags"] -) -def test_vpc_with_route_table(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template33.yaml" - ) - ) - - route_id = stack.outputs["RouteTableId"] - response = aws_client.ec2.describe_route_tables(RouteTableIds=[route_id]) - - # Convert tags to dictionary for easier comparison - response["RouteTables"][0]["Tags"] = { - tag["Key"]: tag["Value"] for tag in response["RouteTables"][0]["Tags"] - } - - snapshot.match("route_table", response) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) - snapshot.add_transformer(snapshot.transform.key_value("RouteTableId")) - snapshot.add_transformer(snapshot.transform.key_value("VpcId")) - - stack.destroy() - - with pytest.raises(aws_client.ec2.exceptions.ClientError): - aws_client.ec2.describe_route_tables(RouteTableIds=[route_id]) - - -@pytest.mark.skip(reason="update doesn't change value for instancetype") -@markers.aws.validated -def test_cfn_update_ec2_instance_type(deploy_cfn_template, aws_client, cleanups): - if aws_client.cloudformation.meta.region_name not in [ - "ap-northeast-1", - "eu-central-1", - "eu-south-1", - "eu-west-1", - "eu-west-2", - "us-east-1", - ]: - pytest.skip() - - key_name = f"testkey-{short_uid()}" - aws_client.ec2.create_key_pair(KeyName=key_name) - cleanups.append(lambda: aws_client.ec2.delete_key_pair(KeyName=key_name)) - - # get alpine image id - if is_aws_cloud(): - images = aws_client.ec2.describe_images( - Filters=[ - {"Name": "name", "Values": ["alpine-3.19.0-x86_64-bios-*"]}, - {"Name": "state", "Values": ["available"]}, - ] - )["Images"] - image_id = images[0]["ImageId"] - else: - image_id = "ami-0a63f96a6a8d4d2c5" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_instance.yml" - ), - parameters={"KeyName": key_name, "InstanceType": "t2.nano", "ImageId": image_id}, - ) - - instance_id = stack.outputs["InstanceId"] - instance = aws_client.ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0][ - "Instances" - ][0] - assert instance["InstanceType"] == "t2.nano" - - deploy_cfn_template( - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_instance.yml" - ), - parameters={"KeyName": key_name, "InstanceType": "t2.medium", "ImageId": image_id}, - is_update=True, - ) - - instance = aws_client.ec2.describe_instances(InstanceIds=[instance_id])["Reservations"][0][ - "Instances" - ][0] - assert instance["InstanceType"] == "t2.medium" - - -@markers.aws.validated -def test_ec2_security_group_id_with_vpc(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_vpc_securitygroup.yml" - ), - ) - - ec2_client = aws_client.ec2 - with_vpcid_sg_group_id = ec2_client.describe_security_groups( - Filters=[ - { - "Name": "group-id", - "Values": [stack.outputs["SGWithVpcIdGroupId"]], - }, - ] - )["SecurityGroups"][0] - without_vpcid_sg_group_id = ec2_client.describe_security_groups( - Filters=[ - { - "Name": "group-id", - "Values": [stack.outputs["SGWithoutVpcIdGroupId"]], - }, - ] - )["SecurityGroups"][0] - - snapshot.add_transformer( - snapshot.transform.regex(with_vpcid_sg_group_id["GroupId"], "") - ) - snapshot.add_transformer( - snapshot.transform.regex(without_vpcid_sg_group_id["GroupId"], "") - ) - snapshot.add_transformer( - snapshot.transform.regex( - without_vpcid_sg_group_id["GroupName"], "" - ) - ) - snapshot.match("references", stack.outputs) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - # fingerprint algorithm is different but presence is ensured by CFn output implementation - "$..ImportedKeyPairFingerprint", - ], -) -def test_keypair_create_import(deploy_cfn_template, snapshot, aws_client): - imported_key_name = f"imported-key-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(imported_key_name, "")) - generated_key_name = f"generated-key-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(generated_key_name, "")) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ec2_import_keypair.yaml" - ), - parameters={"ImportedKeyName": imported_key_name, "GeneratedKeyName": generated_key_name}, - ) - - outputs = stack.outputs - # for the generated key pair, use the EC2 API to get the fingerprint and snapshot the value - key_res = aws_client.ec2.describe_key_pairs(KeyNames=[outputs["GeneratedKeyPairName"]])[ - "KeyPairs" - ][0] - snapshot.add_transformer(snapshot.transform.regex(key_res["KeyFingerprint"], "")) - - snapshot.match("outputs", outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.snapshot.json deleted file mode 100644 index 4b71ac67803dc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.snapshot.json +++ /dev/null @@ -1,303 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_internet_gateway_ref_and_attr": { - "recorded-date": "13-02-2023, 17:13:41", - "recorded-content": { - "outputs": { - "IdAttachment": "", - "RefAttachment": "" - }, - "description": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "Gateway", - "Metadata": {}, - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::EC2::InternetGateway", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_dhcp_options": { - "recorded-date": "19-10-2023, 14:51:28", - "recorded-content": { - "description": { - "DhcpConfigurations": [ - { - "Key": "domain-name", - "Values": [ - { - "Value": "example.com" - } - ] - }, - { - "Key": "domain-name-servers", - "Values": [ - { - "Value": "AmazonProvidedDNS" - } - ] - }, - { - "Key": "netbios-name-servers", - "Values": [ - { - "Value": "10.2.5.1" - } - ] - }, - { - "Key": "netbios-node-type", - "Values": [ - { - "Value": "2" - } - ] - }, - { - "Key": "ntp-servers", - "Values": [ - { - "Value": "10.2.5.1" - } - ] - } - ], - "DhcpOptionsId": "", - "OwnerId": "111111111111", - "Tags": [ - { - "Key": "project", - "Value": "123" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "myDhcpOptions" - }, - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-698b113f" - }, - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-698b113f/d892a0f0-6eb8-11ee-ab19-0a5372e03565" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_transit_gateway_attachment": { - "recorded-date": "08-04-2025, 10:51:02", - "recorded-content": { - "attachment": { - "Association": { - "State": "associated", - "TransitGatewayRouteTableId": "" - }, - "CreationTime": "datetime", - "ResourceId": "", - "ResourceOwnerId": "111111111111", - "ResourceType": "vpc", - "State": "available", - "Tags": [ - { - "Key": "Name", - "Value": "example-tag" - } - ], - "TransitGatewayAttachmentId": "", - "TransitGatewayId": "", - "TransitGatewayOwnerId": "111111111111" - }, - "gateway": { - "CreationTime": "datetime", - "Description": "TGW Route Integration Test", - "Options": { - "AmazonSideAsn": 65000, - "AssociationDefaultRouteTableId": "", - "AutoAcceptSharedAttachments": "disable", - "DefaultRouteTableAssociation": "enable", - "DefaultRouteTablePropagation": "enable", - "DnsSupport": "enable", - "MulticastSupport": "disable", - "PropagationDefaultRouteTableId": "", - "SecurityGroupReferencingSupport": "disable", - "VpnEcmpSupport": "enable" - }, - "OwnerId": "111111111111", - "State": "available", - "Tags": [ - { - "Key": "Application", - "Value": "arn::cloudformation::111111111111:stack/stack-31597705/521e4e40-ecce-11ee-806c-0affc1ff51e7" - } - ], - "TransitGatewayArn": "arn::ec2::111111111111:transit-gateway/", - "TransitGatewayId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_with_route_table": { - "recorded-date": "19-06-2024, 16:48:31", - "recorded-content": { - "route_table": { - "RouteTables": [ - { - "Associations": [], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "100.0.0.0/20", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "Tags": { - "aws:cloudformation:logical-id": "RouteTable", - "aws:cloudformation:stack-id": "", - "aws:cloudformation:stack-name": "", - "env": "production" - }, - "VpcId": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": { - "recorded-date": "01-07-2024, 20:10:52", - "recorded-content": { - "tags": { - "Name": "Suspicious Route Table" - }, - "route_table": { - "Associations": [], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "10.0.0.0/16", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "VpcId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation": { - "recorded-date": "01-07-2024, 20:13:48", - "recorded-content": { - "tags": { - "Name": "Suspicious Route table" - }, - "route_table": { - "Associations": [], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "10.0.0.0/16", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "VpcId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": { - "recorded-date": "02-07-2024, 15:29:41", - "recorded-content": { - "route_table": { - "Associations": [ - { - "AssociationState": { - "State": "associated" - }, - "Main": false, - "RouteTableAssociationId": "", - "RouteTableId": "", - "SubnetId": "" - }, - { - "AssociationState": { - "State": "associated" - }, - "Main": false, - "RouteTableAssociationId": "", - "RouteTableId": "", - "SubnetId": "" - } - ], - "OwnerId": "111111111111", - "PropagatingVgws": [], - "RouteTableId": "", - "Routes": [ - { - "DestinationCidrBlock": "100.0.0.0/20", - "GatewayId": "local", - "Origin": "CreateRouteTable", - "State": "active" - } - ], - "Tags": [ - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-2264231d/d12f4090-3887-11ef-ba9f-0e78e2279133" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "RouteTable" - }, - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-2264231d" - }, - { - "Key": "env", - "Value": "production" - } - ], - "VpcId": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": { - "recorded-date": "19-07-2024, 15:53:16", - "recorded-content": { - "references": { - "SGWithVpcIdGroupId": "", - "SGWithVpcIdRef": "", - "SGWithoutVpcIdGroupId": "", - "SGWithoutVpcIdRef": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_keypair_create_import": { - "recorded-date": "12-08-2024, 21:51:36", - "recorded-content": { - "outputs": { - "GeneratedKeyPairFingerprint": "", - "GeneratedKeyPairName": "", - "ImportedKeyPairFingerprint": "4LmcYnyBOqlloHZ5TKAxfa8BgMK2wL6WeOOTvXVdhmw=", - "ImportedKeyPairName": "" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.validation.json deleted file mode 100644 index 9c06cf509f1a5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.validation.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_update_ec2_instance_type": { - "last_validated_date": "2024-06-19T19:56:42+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_cfn_with_multiple_route_table_associations": { - "last_validated_date": "2024-07-02T15:29:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_dhcp_options": { - "last_validated_date": "2023-10-19T12:51:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_ec2_security_group_id_with_vpc": { - "last_validated_date": "2024-07-19T15:53:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_internet_gateway_ref_and_attr": { - "last_validated_date": "2023-02-13T16:13:41+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_keypair_create_import": { - "last_validated_date": "2024-08-12T21:51:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation": { - "last_validated_date": "2024-07-01T20:13:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_simple_route_table_creation_without_vpc": { - "last_validated_date": "2024-07-01T20:10:52+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_transit_gateway_attachment": { - "last_validated_date": "2025-04-08T10:51:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_creates_default_sg": { - "last_validated_date": "2024-04-01T11:21:54+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ec2.py::test_vpc_with_route_table": { - "last_validated_date": "2024-06-19T16:48:31+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py deleted file mode 100644 index a3619407f9ea5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py +++ /dev/null @@ -1,54 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.skip_offline -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..DomainStatus.AdvancedSecurityOptions.AnonymousAuthEnabled", - "$..DomainStatus.AutoTuneOptions.State", - "$..DomainStatus.ChangeProgressDetails", - "$..DomainStatus.DomainProcessingStatus", - "$..DomainStatus.EBSOptions.VolumeSize", - "$..DomainStatus.ElasticsearchClusterConfig.DedicatedMasterCount", - "$..DomainStatus.ElasticsearchClusterConfig.InstanceCount", - "$..DomainStatus.ElasticsearchClusterConfig.ZoneAwarenessConfig", - "$..DomainStatus.ElasticsearchClusterConfig.ZoneAwarenessEnabled", - "$..DomainStatus.Endpoint", - "$..DomainStatus.ModifyingProperties", - "$..DomainStatus.Processing", - "$..DomainStatus.ServiceSoftwareOptions.CurrentVersion", - ] -) -def test_cfn_handle_elasticsearch_domain(deploy_cfn_template, aws_client, snapshot): - domain_name = f"es-{short_uid()}" - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/elasticsearch_domain.yml" - ) - - deploy_cfn_template(template_path=template_path, parameters={"DomainName": domain_name}) - - rs = aws_client.es.describe_elasticsearch_domain(DomainName=domain_name) - status = rs["DomainStatus"] - snapshot.match("domain", rs) - - tags = aws_client.es.list_tags(ARN=status["ARN"])["TagList"] - snapshot.match("tags", tags) - - snapshot.add_transformer(snapshot.transform.key_value("DomainName")) - snapshot.add_transformer(snapshot.transform.key_value("Endpoint")) - snapshot.add_transformer(snapshot.transform.key_value("TLSSecurityPolicy")) - snapshot.add_transformer(snapshot.transform.key_value("CurrentVersion")) - snapshot.add_transformer(snapshot.transform.key_value("Description")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.snapshot.json deleted file mode 100644 index 427b5a9768e3c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.snapshot.json +++ /dev/null @@ -1,312 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": { - "recorded-date": "02-07-2024, 17:30:21", - "recorded-content": { - "domain": { - "DomainStatus": { - "ARN": "arn::es::111111111111:domain/", - "AccessPolicies": "", - "AdvancedOptions": { - "override_main_response_version": "false", - "rest.action.multi.allow_explicit_index": "true" - }, - "AdvancedSecurityOptions": { - "AnonymousAuthEnabled": false, - "Enabled": false, - "InternalUserDatabaseEnabled": false - }, - "AutoTuneOptions": { - "State": "ENABLED" - }, - "ChangeProgressDetails": { - "ChangeId": "", - "ConfigChangeStatus": "ApplyingChanges", - "InitiatedBy": "CUSTOMER", - "LastUpdatedTime": "datetime", - "StartTime": "datetime" - }, - "CognitoOptions": { - "Enabled": false - }, - "Created": true, - "Deleted": false, - "DomainEndpointOptions": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "" - }, - "DomainId": "111111111111/", - "DomainName": "", - "DomainProcessingStatus": "Creating", - "EBSOptions": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 20, - "VolumeType": "gp2" - }, - "ElasticsearchClusterConfig": { - "ColdStorageOptions": { - "Enabled": false - }, - "DedicatedMasterCount": 3, - "DedicatedMasterEnabled": true, - "DedicatedMasterType": "m3.medium.elasticsearch", - "InstanceCount": 2, - "InstanceType": "m3.medium.elasticsearch", - "WarmEnabled": false, - "ZoneAwarenessConfig": { - "AvailabilityZoneCount": 2 - }, - "ZoneAwarenessEnabled": true - }, - "ElasticsearchVersion": "7.10", - "EncryptionAtRestOptions": { - "Enabled": false - }, - "Endpoint": "search--4kyrgtn4a3gwrja6k4o7nvcrha..es.amazonaws.com", - "ModifyingProperties": [ - { - "ActiveValue": "", - "Name": "AdvancedOptions", - "PendingValue": { - "override_main_response_version": "false", - "rest.action.multi.allow_explicit_index": "true" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.AnonymousAuthDisableDate", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.AnonymousAuthEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.InternalUserDatabaseEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.JWTOptions", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.MasterUserOptions", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "AdvancedSecurityOptions.SAMLOptions", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.ColdStorageOptions", - "PendingValue": { - "Enabled": false - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.DedicatedMasterCount", - "PendingValue": "3", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.DedicatedMasterEnabled", - "PendingValue": "true", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.DedicatedMasterType", - "PendingValue": "m3.medium.elasticsearch", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.InstanceCount", - "PendingValue": "2", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.InstanceType", - "PendingValue": "m3.medium.elasticsearch", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.MultiAZWithStandbyEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmCount", - "PendingValue": "", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmEnabled", - "PendingValue": "false", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmStorage", - "PendingValue": "", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.WarmType", - "PendingValue": "", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchClusterConfig.ZoneAwarenessEnabled", - "PendingValue": "true", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "ElasticsearchVersion", - "PendingValue": "7.10", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "IPAddressType", - "PendingValue": "ipv4", - "ValueType": "PLAIN_TEXT" - }, - { - "ActiveValue": "", - "Name": "TAGS", - "PendingValue": { - "k1": "v1", - "k2": "v2" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "DomainEndpointOptions", - "PendingValue": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "EBSOptions", - "PendingValue": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 20, - "VolumeType": "gp2" - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "EncryptionAtRestOptions", - "PendingValue": { - "Enabled": false - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "NodeToNodeEncryptionOptions", - "PendingValue": { - "Enabled": false - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "OffPeakWindowOptions", - "PendingValue": { - "Enabled": true, - "OffPeakWindow": { - "WindowStartTime": { - "Hours": 2, - "Minutes": 0 - } - } - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "SnapshotOptions", - "PendingValue": { - "AutomatedSnapshotStartHour": 0 - }, - "ValueType": "STRINGIFIED_JSON" - }, - { - "ActiveValue": "", - "Name": "SoftwareUpdateOptions", - "PendingValue": { - "AutoSoftwareUpdateEnabled": false - }, - "ValueType": "STRINGIFIED_JSON" - } - ], - "NodeToNodeEncryptionOptions": { - "Enabled": false - }, - "Processing": false, - "ServiceSoftwareOptions": { - "AutomatedUpdateDate": "datetime", - "Cancellable": false, - "CurrentVersion": "", - "Description": "", - "NewVersion": "", - "OptionalDeployment": true, - "UpdateAvailable": false, - "UpdateStatus": "COMPLETED" - }, - "SnapshotOptions": { - "AutomatedSnapshotStartHour": 0 - }, - "UpgradeProcessing": false - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "tags": [ - { - "Key": "k1", - "Value": "v1" - }, - { - "Key": "k2", - "Value": "v2" - } - ] - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.validation.json deleted file mode 100644 index 879e604d1082c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_elasticsearch.py::test_cfn_handle_elasticsearch_domain": { - "last_validated_date": "2024-07-02T17:30:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py deleted file mode 100644 index 571c6cdfb3f74..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py +++ /dev/null @@ -1,244 +0,0 @@ -import json -import logging -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -LOG = logging.getLogger(__name__) - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_cfn_event_api_destination_resource(deploy_cfn_template, region_name, aws_client): - def _assert(expected_len): - rs = aws_client.events.list_event_buses() - event_buses = [eb for eb in rs["EventBuses"] if eb["Name"] == "my-test-bus"] - assert len(event_buses) == expected_len - rs = aws_client.events.list_connections() - connections = [con for con in rs["Connections"] if con["Name"] == "my-test-conn"] - assert len(connections) == expected_len - rs = aws_client.events.list_api_destinations() - api_destinations = [ - ad for ad in rs["ApiDestinations"] if ad["Name"] == "my-test-destination" - ] - assert len(api_destinations) == expected_len - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_apidestination.yml" - ), - parameters={ - "Region": region_name, - }, - ) - _assert(1) - - stack.destroy() - _assert(0) - - -@markers.aws.validated -def test_eventbus_policies(deploy_cfn_template, aws_client): - event_bus_name = f"event-bus-{short_uid()}" - - stack_response = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/eventbridge_policy.yaml" - ), - parameters={"EventBusName": event_bus_name}, - ) - - describe_response = aws_client.events.describe_event_bus(Name=event_bus_name) - policy = json.loads(describe_response["Policy"]) - assert len(policy["Statement"]) == 2 - - # verify physical resource ID creation - pol1_description = aws_client.cloudformation.describe_stack_resource( - StackName=stack_response.stack_name, LogicalResourceId="eventPolicy" - ) - pol2_description = aws_client.cloudformation.describe_stack_resource( - StackName=stack_response.stack_name, LogicalResourceId="eventPolicy2" - ) - assert ( - pol1_description["StackResourceDetail"]["PhysicalResourceId"] - != pol2_description["StackResourceDetail"]["PhysicalResourceId"] - ) - - deploy_cfn_template( - is_update=True, - stack_name=stack_response.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/eventbridge_policy_singlepolicy.yaml", - ), - parameters={"EventBusName": event_bus_name}, - ) - - describe_response = aws_client.events.describe_event_bus(Name=event_bus_name) - policy = json.loads(describe_response["Policy"]) - assert len(policy["Statement"]) == 1 - - -@markers.aws.validated -def test_eventbus_policy_statement(deploy_cfn_template, aws_client): - event_bus_name = f"event-bus-{short_uid()}" - statement_id = f"statement-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/eventbridge_policy_statement.yaml" - ), - parameters={"EventBusName": event_bus_name, "StatementId": statement_id}, - ) - - describe_response = aws_client.events.describe_event_bus(Name=event_bus_name) - policy = json.loads(describe_response["Policy"]) - assert policy["Version"] == "2012-10-17" - assert len(policy["Statement"]) == 1 - statement = policy["Statement"][0] - assert statement["Sid"] == statement_id - assert statement["Action"] == "events:PutEvents" - assert statement["Principal"] == "*" - assert statement["Effect"] == "Allow" - assert event_bus_name in statement["Resource"] - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_event_rule_to_logs(deploy_cfn_template, aws_client): - event_rule_name = f"event-rule-{short_uid()}" - log_group_name = f"log-group-{short_uid()}" - event_bus_name = f"bus-{short_uid()}" - resource_policy_name = f"policy-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_loggroup.yaml" - ), - parameters={ - "EventRuleName": event_rule_name, - "LogGroupName": log_group_name, - "EventBusName": event_bus_name, - "PolicyName": resource_policy_name, - }, - ) - - log_groups = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_name)["logGroups"] - log_group_names = [lg["logGroupName"] for lg in log_groups] - assert log_group_name in log_group_names - - message_token = f"test-message-{short_uid()}" - resp = aws_client.events.put_events( - Entries=[ - { - "Source": "unittest", - "Resources": [], - "DetailType": "ls-detail-type", - "Detail": json.dumps({"messagetoken": message_token}), - "EventBusName": event_bus_name, - } - ] - ) - assert len(resp["Entries"]) == 1 - - wait_until( - lambda: len(aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"]) - > 0, - 1.0, - 5, - "linear", - ) - log_streams = aws_client.logs.describe_log_streams(logGroupName=log_group_name)["logStreams"] - log_events = aws_client.logs.get_log_events( - logGroupName=log_group_name, logStreamName=log_streams[0]["logStreamName"] - ) - assert message_token in log_events["events"][0]["message"] - - -@markers.aws.validated -def test_event_rule_creation_without_target(deploy_cfn_template, aws_client, snapshot): - event_rule_name = f"event-rule-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(event_rule_name, "event-rule-name")) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_rule_without_targets.yaml" - ), - parameters={"EventRuleName": event_rule_name}, - ) - - response = aws_client.events.describe_rule( - Name=event_rule_name, - ) - snapshot.match("describe_rule", response) - - -@markers.aws.validated -def test_cfn_event_bus_resource(deploy_cfn_template, aws_client): - def _assert(expected_len): - rs = aws_client.events.list_event_buses() - event_buses = [eb for eb in rs["EventBuses"] if eb["Name"] == "my-test-bus"] - assert len(event_buses) == expected_len - rs = aws_client.events.list_connections() - connections = [con for con in rs["Connections"] if con["Name"] == "my-test-conn"] - assert len(connections) == expected_len - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template31.yaml" - ) - ) - _assert(1) - - stack.destroy() - _assert(0) - - -@markers.aws.validated -def test_rule_properties(deploy_cfn_template, aws_client, snapshot): - event_bus_name = f"events-{short_uid()}" - rule_name = f"rule-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(event_bus_name, "")) - snapshot.add_transformer(snapshot.transform.regex(rule_name, "")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_rule_properties.yaml" - ), - parameters={"EventBusName": event_bus_name, "RuleName": rule_name}, - ) - - rule_id = stack.outputs["RuleWithoutNameArn"].rsplit("/")[-1] - snapshot.add_transformer(snapshot.transform.regex(rule_id, "")) - - without_bus_id = stack.outputs["RuleWithoutBusArn"].rsplit("/")[-1] - snapshot.add_transformer(snapshot.transform.regex(without_bus_id, "")) - - snapshot.match("outputs", stack.outputs) - - -@markers.aws.validated -def test_rule_pattern_transformation(aws_client, deploy_cfn_template, snapshot): - """ - The CFn provider for a rule applies a transformation to some properties. Extend this test as more properties or - situations arise. - """ - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/events_rule_pattern.yml" - ), - ) - - rule = aws_client.events.describe_rule(Name=stack.outputs["RuleName"]) - snapshot.match("rule", rule) - snapshot.add_transformer(snapshot.transform.key_value("Name")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.snapshot.json deleted file mode 100644 index 9d0f00f3548f7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.snapshot.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_properties": { - "recorded-date": "01-12-2023, 15:03:52", - "recorded-content": { - "outputs": { - "RuleWithNameArn": "arn::events::111111111111:rule//", - "RuleWithNameRef": "|", - "RuleWithoutBusArn": "arn::events::111111111111:rule/", - "RuleWithoutBusRef": "", - "RuleWithoutNameArn": "arn::events::111111111111:rule//", - "RuleWithoutNameRef": "|" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_pattern_transformation": { - "recorded-date": "08-11-2024, 15:49:06", - "recorded-content": { - "rule": { - "Arn": "arn::events::111111111111:rule/", - "CreatedBy": "111111111111", - "EventBusName": "default", - "EventPattern": { - "detail-type": [ - "Object Created" - ], - "source": [ - "aws.s3" - ], - "detail": { - "bucket": { - "name": [ - "test-s3-bucket" - ] - }, - "object": { - "key": [ - { - "suffix": "/test.json" - } - ] - } - } - }, - "Name": "", - "State": "ENABLED", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_event_rule_creation_without_target": { - "recorded-date": "22-01-2025, 14:15:04", - "recorded-content": { - "describe_rule": { - "Arn": "arn::events::111111111111:rule/event-rule-name", - "CreatedBy": "111111111111", - "EventBusName": "default", - "Name": "event-rule-name", - "ScheduleExpression": "cron(0 1 * * ? *)", - "State": "ENABLED", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.validation.json deleted file mode 100644 index f9456ffe87bad..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_cfn_event_api_destination_resource": { - "last_validated_date": "2024-04-16T06:36:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_event_rule_creation_without_target": { - "last_validated_date": "2025-01-22T14:15:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_eventbus_policy_statement": { - "last_validated_date": "2024-11-14T21:46:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_pattern_transformation": { - "last_validated_date": "2024-11-08T15:49:06+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_events.py::test_rule_properties": { - "last_validated_date": "2023-12-01T14:03:52+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py deleted file mode 100644 index bf3d5a79f2931..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py +++ /dev/null @@ -1,49 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Destinations"]) -def test_firehose_stack_with_kinesis_as_source(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - bucket_name = f"bucket-{short_uid()}" - stream_name = f"stream-{short_uid()}" - delivery_stream_name = f"delivery-stream-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/firehose_kinesis_as_source.yaml" - ), - parameters={ - "BucketName": bucket_name, - "StreamName": stream_name, - "DeliveryStreamName": delivery_stream_name, - }, - max_wait=150, - ) - snapshot.match("outputs", stack.outputs) - - def _assert_stream_available(): - status = aws_client.firehose.describe_delivery_stream( - DeliveryStreamName=delivery_stream_name - ) - assert status["DeliveryStreamDescription"]["DeliveryStreamStatus"] == "ACTIVE" - - retry(_assert_stream_available, sleep=2, retries=15) - - response = aws_client.firehose.describe_delivery_stream(DeliveryStreamName=delivery_stream_name) - assert delivery_stream_name == response["DeliveryStreamDescription"]["DeliveryStreamName"] - snapshot.match("delivery_stream", response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.snapshot.json deleted file mode 100644 index 6bc7b63f87e77..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.snapshot.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": { - "recorded-date": "14-09-2022, 11:19:29", - "recorded-content": { - "outputs": { - "deliveryStreamRef": "" - }, - "delivery_stream": { - "DeliveryStreamDescription": { - "CreateTimestamp": "timestamp", - "DeliveryStreamARN": "arn::firehose::111111111111:deliverystream/", - "DeliveryStreamName": "", - "DeliveryStreamStatus": "ACTIVE", - "DeliveryStreamType": "KinesisStreamAsSource", - "Destinations": [ - { - "DestinationId": "destinationId-000000000001", - "ExtendedS3DestinationDescription": { - "BucketARN": "arn::s3:::", - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64 - }, - "CloudWatchLoggingOptions": { - "Enabled": false - }, - "CompressionFormat": "UNCOMPRESSED", - "DataFormatConversionConfiguration": { - "Enabled": false - }, - "DynamicPartitioningConfiguration": { - "Enabled": true, - "RetryOptions": { - "DurationInSeconds": 300 - } - }, - "EncryptionConfiguration": { - "NoEncryptionConfig": "NoEncryption" - }, - "ErrorOutputPrefix": "firehoseTest-errors/!{firehose:error-output-type}/", - "Prefix": "firehoseTest/!{partitionKeyFromQuery:s3Prefix}", - "ProcessingConfiguration": { - "Enabled": true, - "Processors": [ - { - "Parameters": [ - { - "ParameterName": "MetadataExtractionQuery", - "ParameterValue": "{s3Prefix: .tableName}" - }, - { - "ParameterName": "JsonParsingEngine", - "ParameterValue": "JQ-1.6" - } - ], - "Type": "MetadataExtraction" - } - ] - }, - "RoleARN": "arn::iam::111111111111:role/", - "S3BackupMode": "Disabled" - }, - "S3DestinationDescription": { - "BucketARN": "arn::s3:::", - "BufferingHints": { - "IntervalInSeconds": 60, - "SizeInMBs": 64 - }, - "CloudWatchLoggingOptions": { - "Enabled": false - }, - "CompressionFormat": "UNCOMPRESSED", - "EncryptionConfiguration": { - "NoEncryptionConfig": "NoEncryption" - }, - "ErrorOutputPrefix": "firehoseTest-errors/!{firehose:error-output-type}/", - "Prefix": "firehoseTest/!{partitionKeyFromQuery:s3Prefix}", - "RoleARN": "arn::iam::111111111111:role/" - } - } - ], - "HasMoreDestinations": false, - "Source": { - "KinesisStreamSourceDescription": { - "DeliveryStartTimestamp": "timestamp", - "KinesisStreamARN": "arn::kinesis::111111111111:stream/", - "RoleARN": "arn::iam::111111111111:role/" - } - }, - "VersionId": "1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.validation.json deleted file mode 100644 index e12e5185d82f1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_firehose.py::test_firehose_stack_with_kinesis_as_source": { - "last_validated_date": "2022-09-14T09:19:29+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py deleted file mode 100644 index bb48345710803..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py +++ /dev/null @@ -1,94 +0,0 @@ -import json -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_events_sqs_sns_lambda(deploy_cfn_template, aws_client): - function_name = f"function-{short_uid()}" - queue_name = f"queue-{short_uid()}" - topic_name = f"topic-{short_uid()}" - bus_name = f"bus-{short_uid()}" - rule_name = f"function-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/integration_events_sns_sqs_lambda.yaml", - ), - parameters={ - "FunctionName": function_name, - "QueueName": queue_name, - "TopicName": topic_name, - "BusName": bus_name, - "RuleName": rule_name, - }, - ) - - assert len(stack.outputs) == 7 - lambda_name = stack.outputs["FnName"] - bus_name = stack.outputs["EventBusName"] - - topic_arn = stack.outputs["TopicArn"] - result = aws_client.sns.get_topic_attributes(TopicArn=topic_arn)["Attributes"] - assert json.loads(result.get("Policy")) == { - "Statement": [ - { - "Action": "sns:Publish", - "Effect": "Allow", - "Principal": {"Service": "events.amazonaws.com"}, - "Resource": topic_arn, - "Sid": "0", - } - ], - "Version": "2012-10-17", - } - - # put events - aws_client.events.put_events( - Entries=[ - { - "DetailType": "test-detail-type", - "Detail": '{"app": "localstack"}', - "Source": "test-source", - "EventBusName": bus_name, - }, - ] - ) - - def _check_lambda_invocations(): - groups = aws_client.logs.describe_log_groups( - logGroupNamePrefix=f"/aws/lambda/{lambda_name}" - ) - streams = aws_client.logs.describe_log_streams( - logGroupName=groups["logGroups"][0]["logGroupName"] - ) - assert ( - 0 < len(streams) <= 2 - ) # should be 1 or 2 because of the two potentially simultaneous calls - - all_events = [] - for s in streams["logStreams"]: - events = aws_client.logs.get_log_events( - logGroupName=groups["logGroups"][0]["logGroupName"], - logStreamName=s["logStreamName"], - )["events"] - all_events.extend(events) - - assert [e for e in all_events if topic_name in e["message"]] - assert [e for e in all_events if queue_name in e["message"]] - return True - - assert wait_until(_check_lambda_invocations) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.validation.json deleted file mode 100644 index 4213db8d36bbf..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_integration.py::test_events_sqs_sns_lambda": { - "last_validated_date": "2024-07-02T18:43:06+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py deleted file mode 100644 index f36b7d42f8c25..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py +++ /dev/null @@ -1,181 +0,0 @@ -import json -import os - -import pytest - -from localstack import config -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..StreamDescription.StreamModeDetails"]) -def test_stream_creation(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.resource_name()) - snapshot.add_transformers_list( - [ - snapshot.transform.key_value("StreamName", "stream-name"), - snapshot.transform.key_value("ShardId", "shard-id", reference_replacement=False), - snapshot.transform.key_value("EndingHashKey", "ending-hash-key"), - snapshot.transform.key_value("StartingSequenceNumber", "sequence-number"), - ] - ) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - template = json.dumps( - { - "Resources": { - "TestStream": { - "Type": "AWS::Kinesis::Stream", - "Properties": {"ShardCount": 1}, - }, - }, - "Outputs": { - "StreamNameFromRef": {"Value": {"Ref": "TestStream"}}, - "StreamArnFromAtt": {"Value": {"Fn::GetAtt": "TestStream.Arn"}}, - }, - } - ) - - stack = deploy_cfn_template(template=template) - snapshot.match("stack_output", stack.outputs) - - description = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name) - snapshot.match("resource_description", description) - - stream_name = stack.outputs.get("StreamNameFromRef") - description = aws_client.kinesis.describe_stream(StreamName=stream_name) - snapshot.match("stream_description", description) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..StreamDescription.StreamModeDetails"]) -def test_default_parameters_kinesis(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/kinesis_default.yaml" - ) - ) - - stream_name = stack.outputs["KinesisStreamName"] - rs = aws_client.kinesis.describe_stream(StreamName=stream_name) - snapshot.match("describe_stream", rs) - - snapshot.add_transformer(snapshot.transform.key_value("StreamName")) - snapshot.add_transformer(snapshot.transform.key_value("ShardId")) - snapshot.add_transformer(snapshot.transform.key_value("StartingSequenceNumber")) - - -@markers.aws.validated -def test_cfn_handle_kinesis_firehose_resources(deploy_cfn_template, aws_client): - kinesis_stream_name = f"kinesis-stream-{short_uid()}" - firehose_role_name = f"firehose-role-{short_uid()}" - firehose_stream_name = f"firehose-stream-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_kinesis_stream.yaml" - ), - parameters={ - "KinesisStreamName": kinesis_stream_name, - "DeliveryStreamName": firehose_stream_name, - "KinesisRoleName": firehose_role_name, - }, - ) - - assert len(stack.outputs) == 1 - - rs = aws_client.firehose.describe_delivery_stream(DeliveryStreamName=firehose_stream_name) - assert rs["DeliveryStreamDescription"]["DeliveryStreamARN"] == stack.outputs["MyStreamArn"] - assert rs["DeliveryStreamDescription"]["DeliveryStreamName"] == firehose_stream_name - - rs = aws_client.kinesis.describe_stream(StreamName=kinesis_stream_name) - assert rs["StreamDescription"]["StreamName"] == kinesis_stream_name - - # clean up - stack.destroy() - - rs = aws_client.kinesis.list_streams() - assert kinesis_stream_name not in rs["StreamNames"] - rs = aws_client.firehose.list_delivery_streams() - assert firehose_stream_name not in rs["DeliveryStreamNames"] - - -# TODO: use a different template and move this test to a more generic API level test suite -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify # nothing really works here right now -def test_describe_template(s3_create_bucket, aws_client, cleanups, snapshot): - bucket_name = f"b-{short_uid()}" - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/cfn_kinesis_stream.yaml") - ) - s3_create_bucket(Bucket=bucket_name) - aws_client.s3.put_object(Bucket=bucket_name, Key="template.yml", Body=template_body) - - if is_aws_cloud(): - template_url = ( - f"https://{bucket_name}.s3.{aws_client.s3.meta.region_name}.amazonaws.com/template.yml" - ) - else: - template_url = f"{config.internal_service_url()}/{bucket_name}/template.yml" - - # get summary by template URL - get_template_summary_by_url = aws_client.cloudformation.get_template_summary( - TemplateURL=template_url - ) - snapshot.match("get_template_summary_by_url", get_template_summary_by_url) - - param_keys = {p["ParameterKey"] for p in get_template_summary_by_url["Parameters"]} - assert param_keys == {"KinesisStreamName", "DeliveryStreamName", "KinesisRoleName"} - - # get summary by template body - get_template_summary_by_body = aws_client.cloudformation.get_template_summary( - TemplateBody=template_body - ) - snapshot.match("get_template_summary_by_body", get_template_summary_by_body) - param_keys = {p["ParameterKey"] for p in get_template_summary_by_url["Parameters"]} - assert param_keys == {"KinesisStreamName", "DeliveryStreamName", "KinesisRoleName"} - - -@pytest.mark.skipif( - condition=not is_aws_cloud() and config.DDB_STREAMS_PROVIDER_V2, - reason="Not yet implemented in DDB Streams V2", -) -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..KinesisDataStreamDestinations..DestinationStatusDescription"] -) -def test_dynamodb_stream_response_with_cf(deploy_cfn_template, aws_client, snapshot): - table_name = f"table-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_kinesis_dynamodb.yml" - ), - parameters={"TableName": table_name}, - ) - - response = aws_client.dynamodb.describe_kinesis_streaming_destination(TableName=table_name) - snapshot.match("describe_kinesis_streaming_destination", response) - snapshot.add_transformer(snapshot.transform.key_value("TableName")) - - -@markers.aws.validated -def test_kinesis_stream_consumer_creations(deploy_cfn_template, aws_client): - consumer_name = f"{short_uid()}" - stack = deploy_cfn_template( - parameters={"TestConsumerName": consumer_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/kinesis_stream_consumer.yaml" - ), - ) - consumer_arn = stack.outputs["KinesisSConsumerARN"] - response = aws_client.kinesis.describe_stream_consumer(ConsumerARN=consumer_arn) - assert response["ConsumerDescription"]["ConsumerStatus"] == "ACTIVE" diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.snapshot.json deleted file mode 100644 index 84936b7b55f43..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.snapshot.json +++ /dev/null @@ -1,279 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_stream_creation": { - "recorded-date": "12-09-2022, 14:11:29", - "recorded-content": { - "stack_output": { - "StreamArnFromAtt": "arn::kinesis::111111111111:stream/", - "StreamNameFromRef": "" - }, - "resource_description": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "TestStream", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Kinesis::Stream", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stream_description": { - "StreamDescription": { - "EncryptionType": "NONE", - "EnhancedMonitoring": [ - { - "ShardLevelMetrics": [] - } - ], - "HasMoreShards": false, - "RetentionPeriodHours": 24, - "Shards": [ - { - "HashKeyRange": { - "EndingHashKey": "", - "StartingHashKey": "0" - }, - "SequenceNumberRange": { - "StartingSequenceNumber": "" - }, - "ShardId": "shard-id" - } - ], - "StreamARN": "arn::kinesis::111111111111:stream/", - "StreamCreationTimestamp": "timestamp", - "StreamModeDetails": { - "StreamMode": "PROVISIONED" - }, - "StreamName": "", - "StreamStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_describe_template": { - "recorded-date": "22-05-2023, 09:25:32", - "recorded-content": { - "get_template_summary_by_url": { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]", - "Parameters": [ - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisRoleName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "DeliveryStreamName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisStreamName", - "ParameterType": "String" - } - ], - "ResourceIdentifierSummaries": [ - { - "LogicalResourceIds": [ - "MyBucket" - ], - "ResourceIdentifiers": [ - "BucketName" - ], - "ResourceType": "AWS::S3::Bucket" - }, - { - "LogicalResourceIds": [ - "MyRole" - ], - "ResourceIdentifiers": [ - "RoleName" - ], - "ResourceType": "AWS::IAM::Role" - }, - { - "LogicalResourceIds": [ - "KinesisStream" - ], - "ResourceIdentifiers": [ - "Name" - ], - "ResourceType": "AWS::Kinesis::Stream" - }, - { - "LogicalResourceIds": [ - "DeliveryStream" - ], - "ResourceIdentifiers": [ - "DeliveryStreamName" - ], - "ResourceType": "AWS::KinesisFirehose::DeliveryStream" - } - ], - "ResourceTypes": [ - "AWS::Kinesis::Stream", - "AWS::IAM::Role", - "AWS::S3::Bucket", - "AWS::KinesisFirehose::DeliveryStream" - ], - "Version": "2010-09-09", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_template_summary_by_body": { - "Capabilities": [ - "CAPABILITY_NAMED_IAM" - ], - "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]", - "Parameters": [ - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisRoleName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "DeliveryStreamName", - "ParameterType": "String" - }, - { - "NoEcho": false, - "ParameterConstraints": {}, - "ParameterKey": "KinesisStreamName", - "ParameterType": "String" - } - ], - "ResourceIdentifierSummaries": [ - { - "LogicalResourceIds": [ - "MyBucket" - ], - "ResourceIdentifiers": [ - "BucketName" - ], - "ResourceType": "AWS::S3::Bucket" - }, - { - "LogicalResourceIds": [ - "MyRole" - ], - "ResourceIdentifiers": [ - "RoleName" - ], - "ResourceType": "AWS::IAM::Role" - }, - { - "LogicalResourceIds": [ - "KinesisStream" - ], - "ResourceIdentifiers": [ - "Name" - ], - "ResourceType": "AWS::Kinesis::Stream" - }, - { - "LogicalResourceIds": [ - "DeliveryStream" - ], - "ResourceIdentifiers": [ - "DeliveryStreamName" - ], - "ResourceType": "AWS::KinesisFirehose::DeliveryStream" - } - ], - "ResourceTypes": [ - "AWS::Kinesis::Stream", - "AWS::IAM::Role", - "AWS::S3::Bucket", - "AWS::KinesisFirehose::DeliveryStream" - ], - "Version": "2010-09-09", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_default_parameters_kinesis": { - "recorded-date": "02-07-2024, 18:59:10", - "recorded-content": { - "describe_stream": { - "StreamDescription": { - "EncryptionType": "NONE", - "EnhancedMonitoring": [ - { - "ShardLevelMetrics": [] - } - ], - "HasMoreShards": false, - "RetentionPeriodHours": 24, - "Shards": [ - { - "HashKeyRange": { - "EndingHashKey": "340282366920938463463374607431768211455", - "StartingHashKey": "0" - }, - "SequenceNumberRange": { - "StartingSequenceNumber": "" - }, - "ShardId": "" - } - ], - "StreamARN": "arn::kinesis::111111111111:stream/", - "StreamCreationTimestamp": "timestamp", - "StreamModeDetails": { - "StreamMode": "PROVISIONED" - }, - "StreamName": "", - "StreamStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": { - "recorded-date": "02-07-2024, 19:48:27", - "recorded-content": { - "describe_kinesis_streaming_destination": { - "KinesisDataStreamDestinations": [ - { - "DestinationStatus": "ACTIVE", - "StreamArn": "arn::kinesis::111111111111:stream/EventStream" - } - ], - "TableName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.validation.json deleted file mode 100644 index 70bbffa38d0ee..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_cfn_handle_kinesis_firehose_resources": { - "last_validated_date": "2024-07-02T19:10:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_default_parameters_kinesis": { - "last_validated_date": "2024-07-02T18:59:10+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_describe_template": { - "last_validated_date": "2023-05-22T07:25:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_dynamodb_stream_response_with_cf": { - "last_validated_date": "2024-07-02T19:48:27+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kinesis.py::test_stream_creation": { - "last_validated_date": "2022-09-12T12:11:29+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py deleted file mode 100644 index 6625e3086df75..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py +++ /dev/null @@ -1,77 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_kms_key_disabled(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/kms_key_disabled.yaml" - ) - ) - - key_id = stack.outputs["KeyIdOutput"] - assert key_id - my_key = aws_client.kms.describe_key(KeyId=key_id) - assert not my_key["KeyMetadata"]["Enabled"] - - -@markers.aws.validated -def test_cfn_with_kms_resources(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("KeyAlias")) - - alias_name = f"alias/sample-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template34.yaml" - ), - parameters={"AliasName": alias_name}, - max_wait=300, - ) - snapshot.match("stack-outputs", stack.outputs) - - assert stack.outputs.get("KeyAlias") == alias_name - - def _get_matching_aliases(): - aliases = aws_client.kms.list_aliases()["Aliases"] - return [alias for alias in aliases if alias["AliasName"] == alias_name] - - assert len(_get_matching_aliases()) == 1 - - stack.destroy() - assert not _get_matching_aliases() - - -@markers.aws.validated -def test_deploy_stack_with_kms(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_kms_key.yml" - ), - ) - - assert "KeyId" in stack.outputs - - key_id = stack.outputs["KeyId"] - - stack.destroy() - - def assert_key_deleted(): - resp = aws_client.kms.describe_key(KeyId=key_id)["KeyMetadata"] - assert resp["KeyState"] == "PendingDeletion" - - retry(assert_key_deleted, retries=5, sleep=5) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.snapshot.json deleted file mode 100644 index 6b059512e8448..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.snapshot.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_cfn_with_kms_resources": { - "recorded-date": "29-05-2023, 15:45:17", - "recorded-content": { - "stack-outputs": { - "KeyAlias": "", - "KeyArn": "arn::kms::111111111111:key/" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.validation.json deleted file mode 100644 index 38f9f4302bd86..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_cfn_with_kms_resources": { - "last_validated_date": "2023-05-29T13:45:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_deploy_stack_with_kms": { - "last_validated_date": "2024-07-02T20:23:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_kms.py::test_kms_key_disabled": { - "last_validated_date": "2024-07-02T20:12:46+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py deleted file mode 100644 index 2f5e2cd70ffd2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py +++ /dev/null @@ -1,1379 +0,0 @@ -import base64 -import json -import os -from io import BytesIO - -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack import config -from localstack.aws.api.lambda_ import InvocationType, Runtime, State -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import in_default_partition, is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.aws.arns import get_partition -from localstack.utils.common import short_uid -from localstack.utils.files import load_file -from localstack.utils.http import safe_requests -from localstack.utils.strings import to_bytes, to_str -from localstack.utils.sync import retry, wait_until -from localstack.utils.testutil import create_lambda_archive, get_lambda_log_events - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_lambda_w_dynamodb_event_filter(deploy_cfn_template, aws_client): - function_name = f"test-fn-{short_uid()}" - table_name = f"ddb-tbl-{short_uid()}" - item_to_put = {"id": {"S": "test123"}, "id2": {"S": "test42"}} - item_to_put2 = {"id": {"S": "test123"}, "id2": {"S": "test67"}} - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_dynamodb_filtering.yaml" - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"eventName": ["MODIFY"]}', - }, - ) - - aws_client.dynamodb.put_item(TableName=table_name, Item=item_to_put) - aws_client.dynamodb.put_item(TableName=table_name, Item=item_to_put2) - - def _assert_single_lambda_call(): - events = get_lambda_log_events(function_name, logs_client=aws_client.logs) - assert len(events) == 1 - msg = events[0] - if not isinstance(msg, str): - msg = json.dumps(msg) - assert "MODIFY" in msg and "INSERT" not in msg - - retry(_assert_single_lambda_call, retries=30) - - -@markers.snapshot.skip_snapshot_verify( - [ - # TODO: Fix flaky ESM state mismatch upon update in LocalStack (expected Enabled, actual Disabled) - # This might be a parity issue if AWS does rolling updates (i.e., never disables the ESM upon update). - "$..EventSourceMappings..State", - ] -) -@markers.aws.validated -def test_lambda_w_dynamodb_event_filter_update(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - function_name = f"test-fn-{short_uid()}" - table_name = f"ddb-tbl-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(table_name, "")) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_dynamodb_filtering.yaml" - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"eventName": ["DELETE"]}', - }, - ) - source_mappings = aws_client.lambda_.list_event_source_mappings(FunctionName=function_name) - snapshot.match("source_mappings", source_mappings) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_dynamodb_filtering.yaml" - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"eventName": ["MODIFY"]}', - }, - stack_name=stack.stack_name, - is_update=True, - ) - - source_mappings = aws_client.lambda_.list_event_source_mappings(FunctionName=function_name) - snapshot.match("updated_source_mappings", source_mappings) - - -@markers.aws.validated -def test_update_lambda_function(s3_create_bucket, deploy_cfn_template, aws_client): - function_name = f"lambda-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"Environment": "ORIGINAL", "FunctionName": function_name}, - ) - - response = aws_client.lambda_.get_function(FunctionName=function_name) - assert response["Configuration"]["Environment"]["Variables"]["TEST"] == "ORIGINAL" - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"Environment": "UPDATED", "FunctionName": function_name}, - ) - - response = aws_client.lambda_.get_function(FunctionName=function_name) - assert response["Configuration"]["Environment"]["Variables"]["TEST"] == "UPDATED" - - -@markers.aws.validated -def test_update_lambda_function_name(s3_create_bucket, deploy_cfn_template, aws_client): - function_name_1 = f"lambda-{short_uid()}" - function_name_2 = f"lambda-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"FunctionName": function_name_1}, - ) - - function_name = stack.outputs["LambdaName"] - response = aws_client.lambda_.get_function(FunctionName=function_name_1) - assert response["Configuration"]["Environment"]["Variables"]["TEST"] == "ORIGINAL" - - deploy_cfn_template( - stack_name=stack.stack_name, - is_update=True, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_function_update.yml" - ), - parameters={"FunctionName": function_name_2}, - ) - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_function(FunctionName=function_name) - - aws_client.lambda_.get_function(FunctionName=function_name_2) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Metadata", - "$..DriftInformation", - "$..Type", - "$..Message", - "$..access-control-allow-headers", - "$..access-control-allow-methods", - "$..access-control-allow-origin", - "$..access-control-expose-headers", - "$..server", - "$..content-length", - "$..InvokeMode", - ] -) -@markers.aws.validated -def test_cfn_function_url(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - - deploy = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_url.yaml" - ) - ) - - url_logical_resource_id = "UrlD4FAABD0" - snapshot.add_transformer( - snapshot.transform.regex(url_logical_resource_id, "") - ) - snapshot.add_transformer( - snapshot.transform.key_value( - "FunctionUrl", - ) - ) - snapshot.add_transformer( - snapshot.transform.key_value("x-amzn-trace-id", reference_replacement=False) - ) - snapshot.add_transformer(snapshot.transform.key_value("date", reference_replacement=False)) - - url_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deploy.stack_name, LogicalResourceId=url_logical_resource_id - ) - snapshot.match("url_resource", url_resource) - - url_config = aws_client.lambda_.get_function_url_config( - FunctionName=deploy.outputs["LambdaName"] - ) - snapshot.match("url_config", url_config) - - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException) as e: - aws_client.lambda_.get_function_url_config( - FunctionName=deploy.outputs["LambdaName"], Qualifier="unknownalias" - ) - - snapshot.match("exception_url_config_nonexistent_version", e.value.response) - - url_config_arn = aws_client.lambda_.get_function_url_config( - FunctionName=deploy.outputs["LambdaArn"] - ) - snapshot.match("url_config_arn", url_config_arn) - - response = safe_requests.get(deploy.outputs["LambdaUrl"]) - assert response.ok - assert response.json() == {"hello": "world"} - - lowered_headers = {k.lower(): v for k, v in response.headers.items()} - snapshot.match("response_headers", lowered_headers) - - -@markers.aws.validated -def test_lambda_alias(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda x: x["LogicalResourceId"]), priority=-1 - ) - - function_name = f"function{short_uid()}" - alias_name = f"alias{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(alias_name, "")) - snapshot.add_transformer(snapshot.transform.regex(function_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_alias.yml" - ), - parameters={"FunctionName": function_name, "AliasName": alias_name}, - ) - - invoke_result = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=alias_name, Payload=b"{}" - ) - assert "FunctionError" not in invoke_result - snapshot.match("invoke_result", invoke_result) - - role_arn = aws_client.lambda_.get_function(FunctionName=function_name)["Configuration"]["Role"] - snapshot.add_transformer( - snapshot.transform.regex(role_arn.partition("role/")[-1], ""), priority=-1 - ) - - description = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_name - ) - snapshot.match("stack_resource_descriptions", description) - - alias = aws_client.lambda_.get_alias(FunctionName=function_name, Name=alias_name) - snapshot.match("Alias", alias) - - provisioned_concurrency_config = aws_client.lambda_.get_provisioned_concurrency_config( - FunctionName=function_name, - Qualifier=alias_name, - ) - snapshot.match("provisioned_concurrency_config", provisioned_concurrency_config) - - -@markers.aws.validated -def test_lambda_logging_config(deploy_cfn_template, snapshot, aws_client): - function_name = f"function{short_uid()}" - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(SortingTransformer("StackResources", lambda x: x["LogicalResourceId"])) - snapshot.add_transformer( - snapshot.transform.key_value("LogicalResourceId", reference_replacement=False) - ) - snapshot.add_transformer( - snapshot.transform.key_value("PhysicalResourceId", reference_replacement=False) - ) - snapshot.add_transformer(snapshot.transform.regex(function_name, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_logging_config.yaml" - ), - parameters={"FunctionName": function_name}, - ) - - description = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_name - ) - snapshot.match("stack_resource_descriptions", description) - - logging_config = aws_client.lambda_.get_function(FunctionName=function_name)["Configuration"][ - "LoggingConfig" - ] - snapshot.match("logging_config", logging_config) - - -@pytest.mark.skipif( - not in_default_partition(), reason="Test not applicable in non-default partitions" -) -@markers.aws.validated -def test_lambda_code_signing_config(deploy_cfn_template, snapshot, account_id, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(SortingTransformer("StackResources", lambda x: x["LogicalResourceId"])) - - signer_arn = f"arn:{get_partition(aws_client.lambda_.meta.region_name)}:signer:{aws_client.lambda_.meta.region_name}:{account_id}:/signing-profiles/test" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_code_signing_config.yml" - ), - parameters={"SignerArn": signer_arn}, - ) - - description = aws_client.cloudformation.describe_stack_resources(StackName=stack.stack_name) - snapshot.match("stack_resource_descriptions", description) - - snapshot.match( - "config", - aws_client.lambda_.get_code_signing_config(CodeSigningConfigArn=stack.outputs["Arn"]), - ) - - -@markers.aws.validated -def test_event_invoke_config(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_event_invoke_config.yml" - ), - max_wait=180, - ) - - event_invoke_config = aws_client.lambda_.get_function_event_invoke_config( - FunctionName=stack.outputs["FunctionName"], - Qualifier=stack.outputs["FunctionQualifier"], - ) - - snapshot.match("event_invoke_config", event_invoke_config) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda ZIP flaky in CI - "$..CodeSize", - ] -) -@markers.aws.validated -def test_lambda_version(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]) - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_version.yaml" - ), - max_wait=180, - ) - function_name = deployment.outputs["FunctionName"] - function_version = deployment.outputs["FunctionVersion"] - - invoke_result = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=function_version, Payload=b"{}" - ) - assert "FunctionError" not in invoke_result - snapshot.match("invoke_result", invoke_result) - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - snapshot.match("stack_resources", stack_resources) - - versions_by_fn = aws_client.lambda_.list_versions_by_function(FunctionName=function_name) - get_function_version = aws_client.lambda_.get_function( - FunctionName=function_name, Qualifier=function_version - ) - - snapshot.match("versions_by_fn", versions_by_fn) - snapshot.match("get_function_version", get_function_version) - - -@markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda ZIP flaky in CI - "$..CodeSize", - ] -) -@markers.aws.validated -def test_lambda_version_provisioned_concurrency(deploy_cfn_template, snapshot, aws_client): - """Provisioned concurrency slows down the test case considerably (~2min 40s on AWS) - because CloudFormation waits until the provisioned Lambda functions are ready. - """ - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]) - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_version_provisioned_concurrency.yaml", - ), - max_wait=240, - ) - function_name = deployment.outputs["FunctionName"] - function_version = deployment.outputs["FunctionVersion"] - - invoke_result = aws_client.lambda_.invoke( - FunctionName=function_name, Qualifier=function_version, Payload=b"{}" - ) - assert "FunctionError" not in invoke_result - snapshot.match("invoke_result", invoke_result) - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - snapshot.match("stack_resources", stack_resources) - - versions_by_fn = aws_client.lambda_.list_versions_by_function(FunctionName=function_name) - get_function_version = aws_client.lambda_.get_function( - FunctionName=function_name, Qualifier=function_version - ) - - snapshot.match("versions_by_fn", versions_by_fn) - snapshot.match("get_function_version", get_function_version) - - provisioned_concurrency_config = aws_client.lambda_.get_provisioned_concurrency_config( - FunctionName=function_name, - Qualifier=function_version, - ) - snapshot.match("provisioned_concurrency_config", provisioned_concurrency_config) - - -@markers.aws.validated -def test_lambda_cfn_run(deploy_cfn_template, aws_client): - """ - simply deploys a lambda and immediately invokes it - """ - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_simple.yaml" - ), - max_wait=120, - ) - fn_name = deployment.outputs["FunctionName"] - assert ( - aws_client.lambda_.get_function(FunctionName=fn_name)["Configuration"]["State"] - == State.Active - ) - aws_client.lambda_.invoke(FunctionName=fn_name, LogType="Tail", Payload=b"{}") - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.only_localstack(reason="This is functionality specific to Localstack") -def test_lambda_cfn_run_with_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch -): - """ - deploys the same lambda with an empty CFN string deny list, testing that it behaves as expected - (i.e. the URLs in the deny list are modified) - """ - monkeypatch.setattr(config, "CFN_STRING_REPLACEMENT_DENY_LIST", []) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", - ), - max_wait=120, - ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] - # URLs that match regex to capture AWS URLs gets Localstack port appended - non-matching URLs remain unchanged. - assert function_env_variables["API_URL_1"] == "https://api.example.com" - assert ( - function_env_variables["API_URL_2"] - == "https://storage.execute-api.amazonaws.com:4566/test-resource" - ) - assert ( - function_env_variables["API_URL_3"] - == "https://reporting.execute-api.amazonaws.com:4566/test-resource" - ) - assert ( - function_env_variables["API_URL_4"] - == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" - ) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.only_localstack(reason="This is functionality specific to Localstack") -def test_lambda_cfn_run_with_non_empty_string_replacement_deny_list( - deploy_cfn_template, aws_client, monkeypatch -): - """ - deploys the same lambda with a non-empty CFN string deny list configurations, testing that it behaves as expected - (i.e. the URLs in the deny list are not modified) - """ - monkeypatch.setattr( - config, - "CFN_STRING_REPLACEMENT_DENY_LIST", - [ - "https://storage.execute-api.us-east-2.amazonaws.com/test-resource", - "https://reporting.execute-api.us-east-1.amazonaws.com/test-resource", - ], - ) - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml", - ), - max_wait=120, - ) - function = aws_client.lambda_.get_function(FunctionName=deployment.outputs["FunctionName"]) - function_env_variables = function["Configuration"]["Environment"]["Variables"] - # URLs that match regex to capture AWS URLs but are explicitly in the deny list, don't get modified - - # non-matching URLs remain unchanged. - assert function_env_variables["API_URL_1"] == "https://api.example.com" - assert ( - function_env_variables["API_URL_2"] - == "https://storage.execute-api.us-east-2.amazonaws.com/test-resource" - ) - assert ( - function_env_variables["API_URL_3"] - == "https://reporting.execute-api.us-east-1.amazonaws.com/test-resource" - ) - assert ( - function_env_variables["API_URL_4"] - == "https://blockchain.execute-api.amazonaws.com:4566/test-resource" - ) - - -@pytest.mark.skip(reason="broken/notimplemented") -@markers.aws.validated -def test_lambda_vpc(deploy_cfn_template, aws_client): - """ - this test showcases a very long-running deployment of a fairly straight forward lambda function - cloudformation will poll get_function until the active state has been reached - """ - fn_name = f"vpc-lambda-fn-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_vpc.yaml" - ), - parameters={ - "FunctionNameParam": fn_name, - }, - max_wait=600, - ) - assert ( - aws_client.lambda_.get_function(FunctionName=fn_name)["Configuration"]["State"] - == State.Active - ) - aws_client.lambda_.invoke(FunctionName=fn_name, LogType="Tail", Payload=b"{}") - - -@markers.aws.validated -def test_update_lambda_permissions(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_permission.yml" - ) - ) - - new_principal = aws_client.sts.get_caller_identity()["Account"] - - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - parameters={"PrincipalForPermission": new_principal}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_permission.yml" - ), - ) - - policy = aws_client.lambda_.get_policy(FunctionName=stack.outputs["FunctionName"]) - - # The behaviour of thi principal acocunt setting changes with aws or lambda providers - principal = json.loads(policy["Policy"])["Statement"][0]["Principal"] - if isinstance(principal, dict): - principal = principal.get("AWS") or principal.get("Service", "") - - assert new_principal in principal - - -@markers.aws.validated -def test_multiple_lambda_permissions_for_singlefn(deploy_cfn_template, snapshot, aws_client): - deploy = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_permission_multiple.yaml", - ), - max_wait=240, - ) - fn_name = deploy.outputs["LambdaName"] - p1_sid = deploy.outputs["PermissionLambda"] - p2_sid = deploy.outputs["PermissionStates"] - - snapshot.add_transformer(snapshot.transform.regex(p1_sid, "")) - snapshot.add_transformer(snapshot.transform.regex(p2_sid, "")) - snapshot.add_transformer(snapshot.transform.regex(fn_name, "")) - snapshot.add_transformer(SortingTransformer("Statement", lambda s: s["Sid"])) - - policy = aws_client.lambda_.get_policy(FunctionName=fn_name) - # load the policy json, so we can properly snapshot it - policy["Policy"] = json.loads(policy["Policy"]) - snapshot.match("policy", policy) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - # Added by CloudFormation - "$..Tags.'aws:cloudformation:logical-id'", - "$..Tags.'aws:cloudformation:stack-id'", - "$..Tags.'aws:cloudformation:stack-name'", - ] -) -def test_lambda_function_tags(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - - function_name = f"fn-{short_uid()}" - environment = f"dev-{short_uid()}" - snapshot.add_transformer(snapshot.transform.regex(environment, "")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_with_tags.yml", - ), - parameters={ - "FunctionName": function_name, - "Environment": environment, - }, - ) - snapshot.add_transformer(snapshot.transform.regex(deployment.stack_name, "")) - - get_function_result = aws_client.lambda_.get_function(FunctionName=function_name) - snapshot.match("get_function_result", get_function_result) - - -class TestCfnLambdaIntegrations: - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.EffectiveDeliveryPolicy", # broken in sns right now. needs to be wrapped within an http key - "$..Attributes.DeliveryPolicy", # shouldn't be there - "$..Attributes.Policy", # missing SNS:Receive - "$..CodeSize", - "$..Configuration.Layers", - "$..Tags", # missing cloudformation automatic resource tags for the lambda function - ] - ) - @markers.aws.validated - def test_cfn_lambda_permissions(self, deploy_cfn_template, snapshot, aws_client): - """ - * Lambda Function - * Lambda Permission - * SNS Topic - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer( - snapshot.transform.key_value("Sid"), priority=-1 - ) # TODO: need a better snapshot construct here - # Sid format: e.g. `-6JTUCQQ17UXN` - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_sns_permissions.yaml", - ), - max_wait=240, - ) - - # verify by checking APIs - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - snapshot.match("stack_resources", stack_resources) - - fn_name = deployment.outputs["FunctionName"] - topic_arn = deployment.outputs["TopicArn"] - - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_topic_attributes_result = aws_client.sns.get_topic_attributes(TopicArn=topic_arn) - get_policy_result = aws_client.lambda_.get_policy(FunctionName=fn_name) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_topic_attributes_result", get_topic_attributes_result) - snapshot.match("get_policy_result", get_policy_result) - - # check that lambda is invoked - - msg = f"msg-verification-{short_uid()}" - aws_client.sns.publish(Message=msg, TopicArn=topic_arn) - - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda - "$..Tags", - "$..Configuration.CodeSize", # Lambda ZIP flaky in CI - # SQS - "$..Attributes.SqsManagedSseEnabled", - # IAM - "$..PolicyNames", - "$..PolicyName", - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..StackResources..PhysicalResourceId", # TODO: compatibility between AWS URL and localstack URL - ] - ) - @markers.aws.validated - def test_cfn_lambda_sqs_source(self, deploy_cfn_template, snapshot, aws_client): - """ - Resources: - * Lambda Function - * SQS Queue - * EventSourceMapping - * IAM Roles/Policies (e.g. sqs:ReceiveMessage for lambda service to poll SQS) - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.sns_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer(snapshot.transform.key_value("RoleId")) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_sqs_source.yaml" - ), - max_wait=240, - ) - fn_name = deployment.outputs["FunctionName"] - queue_url = deployment.outputs["QueueUrl"] - esm_id = deployment.outputs["ESMId"] - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - - # IAM::Policy seems to have a pretty weird physical resource ID (e.g. stack-fnSe-3OZPF82JL41D) - iam_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deployment.stack_id, LogicalResourceId="fnServiceRoleDefaultPolicy0ED5D3E5" - ) - snapshot.add_transformer( - snapshot.transform.regex( - iam_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - - snapshot.match("stack_resources", stack_resources) - - # query service APIs for resource states - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_esm_result = aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - get_queue_atts_result = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url, AttributeNames=["All"] - ) - role_arn = get_function_result["Configuration"]["Role"] - role_name = role_arn.partition("role/")[-1] - get_role_result = aws_client.iam.get_role(RoleName=role_name) - list_attached_role_policies_result = aws_client.iam.list_attached_role_policies( - RoleName=role_name - ) - list_inline_role_policies_result = aws_client.iam.list_role_policies(RoleName=role_name) - policies = [] - for rp in list_inline_role_policies_result["PolicyNames"]: - get_rp_result = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=rp) - policies.append(get_rp_result) - - snapshot.add_transformer( - snapshot.transform.jsonpath( - "$..policies..ResponseMetadata", "", reference_replacement=False - ) - ) - - snapshot.match("role_policies", {"policies": policies}) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_esm_result", get_esm_result) - snapshot.match("get_queue_atts_result", get_queue_atts_result) - snapshot.match("get_role_result", get_role_result) - snapshot.match("list_attached_role_policies_result", list_attached_role_policies_result) - snapshot.match("list_inline_role_policies_result", list_inline_role_policies_result) - - # TODO: extract - # TODO: is this even necessary? should the cloudformation deployment guarantee that this is enabled already? - def wait_esm_active(): - try: - return ( - aws_client.lambda_.get_event_source_mapping(UUID=esm_id)["State"] == "Enabled" - ) - except Exception as e: - print(e) - - assert wait_until(wait_esm_active) - - msg = f"msg-verification-{short_uid()}" - aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody=msg) - - # TODO: extract - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - deployment.destroy() - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - # TODO: consider moving into the dedicated DynamoDB => Lambda tests because it tests the filtering functionality rather than CloudFormation (just using CF to deploy resources) - # tests.aws.services.lambda_.test_lambda_integration_dynamodbstreams.TestDynamoDBEventSourceMapping.test_dynamodb_event_filter - @markers.aws.validated - def test_lambda_dynamodb_event_filter( - self, dynamodb_wait_for_table_active, deploy_cfn_template, aws_client, monkeypatch - ): - function_name = f"test-fn-{short_uid()}" - table_name = f"ddb-tbl-{short_uid()}" - - item_to_put = { - "PK": {"S": "person1"}, - "SK": {"S": "details"}, - "name": {"S": "John Doe"}, - } - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/lambda_dynamodb_event_filter.yaml", - ), - parameters={ - "FunctionName": function_name, - "TableName": table_name, - "Filter": '{"dynamodb": {"NewImage": {"homemade": {"S": [{"exists": false}]}}}}', - }, - ) - aws_client.dynamodb.put_item(TableName=table_name, Item=item_to_put) - - def _send_events(): - log_events = aws_client.logs.filter_log_events( - logGroupName=f"/aws/lambda/{function_name}" - )["events"] - return any("Hello world!" in e["message"] for e in log_events) - - sleep = 10 if os.getenv("TEST_TARGET") == "AWS_CLOUD" else 1 - assert wait_until(_send_events, wait=sleep, max_retries=50) - - @markers.snapshot.skip_snapshot_verify( - paths=[ - # Lambda - "$..Tags", - "$..Configuration.CodeSize", # Lambda ZIP flaky in CI - # IAM - "$..PolicyNames", - "$..policies..PolicyName", - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..StackResources..LogicalResourceId", - "$..StackResources..PhysicalResourceId", - # dynamodb describe_table - "$..Table.ProvisionedThroughput.LastDecreaseDateTime", - "$..Table.ProvisionedThroughput.LastIncreaseDateTime", - "$..Table.Replicas", - # stream result - "$..StreamDescription.CreationRequestDateTime", - ] - ) - @markers.aws.validated - def test_cfn_lambda_dynamodb_source(self, deploy_cfn_template, snapshot, aws_client): - """ - Resources: - * Lambda Function - * DynamoDB Table + Stream - * EventSourceMapping - * IAM Roles/Policies (e.g. dynamodb:GetRecords for lambda service to poll dynamodb) - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.dynamodb_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer(snapshot.transform.key_value("RoleId")) - snapshot.add_transformer( - snapshot.transform.key_value("ShardId", reference_replacement=False) - ) - snapshot.add_transformer( - snapshot.transform.key_value("StartingSequenceNumber", reference_replacement=False) - ) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/cfn_lambda_dynamodb_source.yaml", - ), - max_wait=240, - ) - fn_name = deployment.outputs["FunctionName"] - table_name = deployment.outputs["TableName"] - stream_arn = deployment.outputs["StreamArn"] - esm_id = deployment.outputs["ESMId"] - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - - # IAM::Policy seems to have a pretty weird physical resource ID (e.g. stack-fnSe-3OZPF82JL41D) - iam_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deployment.stack_id, LogicalResourceId="fnServiceRoleDefaultPolicy0ED5D3E5" - ) - snapshot.add_transformer( - snapshot.transform.regex( - iam_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - - snapshot.match("stack_resources", stack_resources) - - # query service APIs for resource states - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_esm_result = aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - describe_table_result = aws_client.dynamodb.describe_table(TableName=table_name) - describe_stream_result = aws_client.dynamodbstreams.describe_stream(StreamArn=stream_arn) - role_arn = get_function_result["Configuration"]["Role"] - role_name = role_arn.partition("role/")[-1] - get_role_result = aws_client.iam.get_role(RoleName=role_name) - list_attached_role_policies_result = aws_client.iam.list_attached_role_policies( - RoleName=role_name - ) - list_inline_role_policies_result = aws_client.iam.list_role_policies(RoleName=role_name) - policies = [] - for rp in list_inline_role_policies_result["PolicyNames"]: - get_rp_result = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=rp) - policies.append(get_rp_result) - - snapshot.add_transformer( - snapshot.transform.jsonpath( - "$..policies..ResponseMetadata", "", reference_replacement=False - ) - ) - - snapshot.match("role_policies", {"policies": policies}) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_esm_result", get_esm_result) - snapshot.match("describe_table_result", describe_table_result) - snapshot.match("describe_stream_result", describe_stream_result) - snapshot.match("get_role_result", get_role_result) - snapshot.match("list_attached_role_policies_result", list_attached_role_policies_result) - snapshot.match("list_inline_role_policies_result", list_inline_role_policies_result) - - # TODO: extract - # TODO: is this even necessary? should the cloudformation deployment guarantee that this is enabled already? - def wait_esm_active(): - try: - return ( - aws_client.lambda_.get_event_source_mapping(UUID=esm_id)["State"] == "Enabled" - ) - except Exception as e: - print(e) - - assert wait_until(wait_esm_active) - - msg = f"msg-verification-{short_uid()}" - aws_client.dynamodb.put_item( - TableName=table_name, Item={"id": {"S": "test"}, "msg": {"S": msg}} - ) - - # TODO: extract - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - deployment.destroy() - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Role.Description", - "$..Role.MaxSessionDuration", - "$..Configuration.CodeSize", - "$..Tags", - # TODO: wait for ESM to become active in CloudFormation to mitigate these flaky fields - "$..Configuration.LastUpdateStatus", - "$..Configuration.State", - "$..Configuration.StateReason", - "$..Configuration.StateReasonCode", - ], - ) - @markers.aws.validated - def test_cfn_lambda_kinesis_source(self, deploy_cfn_template, snapshot, aws_client): - """ - Resources: - * Lambda Function - * Kinesis Stream - * EventSourceMapping - * IAM Roles/Policies (e.g. kinesis:GetRecords for lambda service to poll kinesis) - """ - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.kinesis_api()) - snapshot.add_transformer( - SortingTransformer("StackResources", lambda sr: sr["LogicalResourceId"]), priority=-1 - ) - snapshot.add_transformer(snapshot.transform.key_value("CodeSha256")) - snapshot.add_transformer(snapshot.transform.key_value("RoleId")) - snapshot.add_transformer( - snapshot.transform.key_value("ShardId", reference_replacement=False) - ) - snapshot.add_transformer( - snapshot.transform.key_value("StartingSequenceNumber", reference_replacement=False) - ) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_kinesis_source.yaml" - ), - max_wait=240, - ) - fn_name = deployment.outputs["FunctionName"] - stream_name = deployment.outputs["StreamName"] - # stream_arn = deployment.outputs["StreamArn"] - esm_id = deployment.outputs["ESMId"] - - stack_resources = aws_client.cloudformation.describe_stack_resources( - StackName=deployment.stack_id - ) - - # IAM::Policy seems to have a pretty weird physical resource ID (e.g. stack-fnSe-3OZPF82JL41D) - iam_policy_resource = aws_client.cloudformation.describe_stack_resource( - StackName=deployment.stack_id, LogicalResourceId="fnServiceRoleDefaultPolicy0ED5D3E5" - ) - snapshot.add_transformer( - snapshot.transform.regex( - iam_policy_resource["StackResourceDetail"]["PhysicalResourceId"], - "", - ) - ) - - snapshot.match("stack_resources", stack_resources) - - # query service APIs for resource states - get_function_result = aws_client.lambda_.get_function(FunctionName=fn_name) - get_esm_result = aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - describe_stream_result = aws_client.kinesis.describe_stream(StreamName=stream_name) - role_arn = get_function_result["Configuration"]["Role"] - role_name = role_arn.partition("role/")[-1] - get_role_result = aws_client.iam.get_role(RoleName=role_name) - list_attached_role_policies_result = aws_client.iam.list_attached_role_policies( - RoleName=role_name - ) - list_inline_role_policies_result = aws_client.iam.list_role_policies(RoleName=role_name) - policies = [] - for rp in list_inline_role_policies_result["PolicyNames"]: - get_rp_result = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName=rp) - policies.append(get_rp_result) - - snapshot.add_transformer( - snapshot.transform.jsonpath( - "$..policies..ResponseMetadata", "", reference_replacement=False - ) - ) - - snapshot.match("role_policies", {"policies": policies}) - snapshot.match("get_function_result", get_function_result) - snapshot.match("get_esm_result", get_esm_result) - snapshot.match("describe_stream_result", describe_stream_result) - snapshot.match("get_role_result", get_role_result) - snapshot.match("list_attached_role_policies_result", list_attached_role_policies_result) - snapshot.match("list_inline_role_policies_result", list_inline_role_policies_result) - - # TODO: extract - # TODO: is this even necessary? should the cloudformation deployment guarantee that this is enabled already? - def wait_esm_active(): - try: - return ( - aws_client.lambda_.get_event_source_mapping(UUID=esm_id)["State"] == "Enabled" - ) - except Exception as e: - print(e) - - assert wait_until(wait_esm_active) - - msg = f"msg-verification-{short_uid()}" - data_msg = to_str(base64.b64encode(to_bytes(msg))) - aws_client.kinesis.put_record( - StreamName=stream_name, Data=msg, PartitionKey="samplepartitionkey" - ) - - # TODO: extract - def wait_logs(): - log_events = aws_client.logs.filter_log_events(logGroupName=f"/aws/lambda/{fn_name}")[ - "events" - ] - return any(data_msg in e["message"] for e in log_events) - - assert wait_until(wait_logs) - - deployment.destroy() - - with pytest.raises(aws_client.lambda_.exceptions.ResourceNotFoundException): - aws_client.lambda_.get_event_source_mapping(UUID=esm_id) - - -class TestCfnLambdaDestinations: - """ - generic cases - 1. verify payload - - - [ ] SNS destination success - - [ ] SNS destination failure - - [ ] SQS destination success - - [ ] SQS destination failure - - [ ] Lambda destination success - - [ ] Lambda destination failure - - [ ] EventBridge destination success - - [ ] EventBridge destination failure - - meta cases - * test max event age - * test retry count - * qualifier issues - * reserved concurrency set to 0 => should immediately go to failure destination / dlq - * combination with DLQ - * test with a very long queue (reserved concurrency 1, high function duration, low max event age) - - edge cases - - [ ] Chaining async lambdas - - doc: - "If the function doesn't have enough concurrency available to process all events, additional requests are throttled. - For throttling errors (429) and system errors (500-series), Lambda returns the event to the queue and attempts to run the function again for up to 6 hours. - The retry interval increases exponentially from 1 second after the first attempt to a maximum of 5 minutes. - If the queue contains many entries, Lambda increases the retry interval and reduces the rate at which it reads events from the queue." - - """ - - @pytest.mark.parametrize( - ["on_success", "on_failure"], - [ - ("sqs", "sqs"), - # TODO: test needs further work - # ("sns", "sns"), - # ("lambda", "lambda"), - # ("eventbridge", "eventbridge") - ], - ) - @markers.aws.validated - def test_generic_destination_routing( - self, deploy_cfn_template, on_success, on_failure, aws_client - ): - """ - This fairly simple template lets us choose between the 4 different destinations for both OnSuccess as well as OnFailure. - The template chooses between one of 4 ARNs via indexed access according to this mapping: - - 0: SQS - 1: SNS - 2: Lambda - 3: EventBridge - - All of them are connected downstream to another Lambda function. - This function can be used to verify that the payload has propagated through the hole scenario. - It also allows us to verify the specific payload format depending on the service integration. - - │ - ▼ - Lambda - │ - ┌──────┬───┴───┬───────┐ - │ │ │ │ - ▼ ▼ ▼ ▼ - (direct) SQS SNS EventBridge - │ │ │ │ - │ │ │ │ - └──────┴───┬───┴───────┘ - │ - ▼ - Lambda - - # TODO: fix eventbridge name (reuse?) - """ - - name_to_index_map = {"sqs": "0", "sns": "1", "lambda": "2", "eventbridge": "3"} - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_destinations.yaml" - ), - parameters={ - # "RetryParam": "", - # "MaxEventAgeSecondsParam": "", - # "QualifierParameter": "", - "OnSuccessSwitch": name_to_index_map[on_success], - "OnFailureSwitch": name_to_index_map[on_failure], - }, - max_wait=600, - ) - - invoke_fn_name = deployment.outputs["LambdaName"] - collect_fn_name = deployment.outputs["CollectLambdaName"] - - msg = f"message-{short_uid()}" - - # Success case - aws_client.lambda_.invoke( - FunctionName=invoke_fn_name, - Payload=to_bytes(json.dumps({"message": msg, "should_fail": "0"})), - InvocationType=InvocationType.Event, - ) - - # Failure case - aws_client.lambda_.invoke( - FunctionName=invoke_fn_name, - Payload=to_bytes(json.dumps({"message": msg, "should_fail": "1"})), - InvocationType=InvocationType.Event, - ) - - def wait_for_logs(): - events = aws_client.logs.filter_log_events( - logGroupName=f"/aws/lambda/{collect_fn_name}" - )["events"] - message_events = [e["message"] for e in events if msg in e["message"]] - return len(message_events) >= 2 - # return len(events) >= 6 # note: each invoke comes with at least 3 events even without printing - - wait_until(wait_for_logs) - - -@markers.aws.validated -def test_python_lambda_code_deployed_via_s3(deploy_cfn_template, aws_client, s3_bucket): - bucket_key = "handler.zip" - zip_file = create_lambda_archive( - load_file( - os.path.join(os.path.dirname(__file__), "../../../../lambda_/functions/lambda_echo.py") - ), - get_content=True, - runtime=Runtime.python3_12, - ) - aws_client.s3.upload_fileobj(BytesIO(zip_file), s3_bucket, bucket_key) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_s3_code.yaml" - ), - parameters={ - "LambdaCodeBucket": s3_bucket, - "LambdaRuntime": "python3.10", - "LambdaHandler": "handler.handler", - }, - ) - - function_name = deployment.outputs["LambdaName"] - invocation_result = aws_client.lambda_.invoke( - FunctionName=function_name, Payload=json.dumps({"hello": "world"}) - ) - payload = json.load(invocation_result["Payload"]) - assert payload == {"hello": "world"} - assert invocation_result["StatusCode"] == 200 - - -@markers.aws.validated -def test_lambda_cfn_dead_letter_config_async_invocation( - deploy_cfn_template, aws_client, s3_create_bucket, snapshot -): - # invoke intentionally failing lambda async, which then forwards to the DLQ as configured. - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.sqs_api()) - - # cfn template was generated via serverless, but modified to work with pure cloudformation - s3_bucket = s3_create_bucket() - bucket_key = "serverless/dlq/local/1701682216701-2023-12-04T09:30:16.701Z/dlq.zip" - - zip_file = create_lambda_archive( - load_file( - os.path.join( - os.path.dirname(__file__), "../../../../lambda_/functions/lambda_handler_error.py" - ) - ), - get_content=True, - runtime=Runtime.python3_12, - ) - aws_client.s3.upload_fileobj(BytesIO(zip_file), s3_bucket, bucket_key) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_lambda_serverless.yml" - ), - parameters={"LambdaCodeBucket": s3_bucket}, - ) - function_name = deployment.outputs["LambdaName"] - - # async invocation - aws_client.lambda_.invoke(FunctionName=function_name, InvocationType="Event") - dlq_queue = deployment.outputs["DLQName"] - response = {} - - def check_dlq_message(response: dict): - response.update(aws_client.sqs.receive_message(QueueUrl=dlq_queue, VisibilityTimeout=0)) - assert response.get("Messages") - - retry(check_dlq_message, response=response, retries=5, sleep=2.5) - snapshot.match("failed-async-lambda", response) - - -@markers.aws.validated -def test_lambda_layer_crud(deploy_cfn_template, aws_client, s3_bucket, snapshot): - snapshot.add_transformers_list( - [snapshot.transform.key_value("LambdaName"), snapshot.transform.key_value("layer-name")] - ) - - layer_name = f"layer-{short_uid()}" - snapshot.match("layer-name", layer_name) - - bucket_key = "layer.zip" - zip_file = create_lambda_archive( - "hello", - get_content=True, - runtime=Runtime.python3_12, - file_name="hello.txt", - ) - aws_client.s3.upload_fileobj(BytesIO(zip_file), s3_bucket, bucket_key) - - deployment = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/lambda_layer_version.yml" - ), - parameters={"LayerBucket": s3_bucket, "LayerName": layer_name}, - ) - snapshot.match("cfn-output", deployment.outputs) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.snapshot.json deleted file mode 100644 index f5743e2e003e4..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.snapshot.json +++ /dev/null @@ -1,1892 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_cfn_function_url": { - "recorded-date": "16-04-2024, 08:16:02", - "recorded-content": { - "url_resource": { - "StackResourceDetail": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "", - "Metadata": {}, - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Url", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "url_config": { - "AuthType": "NONE", - "CreationTime": "date", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionUrl": "", - "InvokeMode": "BUFFERED", - "LastModifiedTime": "date", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "exception_url_config_nonexistent_version": { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "The resource you requested does not exist." - }, - "Message": "The resource you requested does not exist.", - "Type": "User", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 404 - } - }, - "url_config_arn": { - "AuthType": "NONE", - "CreationTime": "date", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionUrl": "", - "InvokeMode": "BUFFERED", - "LastModifiedTime": "date", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "response_headers": { - "connection": "keep-alive", - "content-length": "17", - "content-type": "application/json", - "date": "date", - "x-amzn-requestid": "", - "x-amzn-trace-id": "x-amzn-trace-id" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_alias": { - "recorded-date": "07-05-2025, 15:39:26", - "recorded-content": { - "invoke_result": { - "ExecutedVersion": "1", - "Payload": { - "function_version": "1", - "initialization_type": null - }, - "StatusCode": 200, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "FunctionAlias", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Alias", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "LambdaFunction", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "MyFnServiceRole", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Version", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "Alias": { - "AliasArn": "arn::lambda::111111111111:function:", - "Description": "", - "FunctionVersion": "1", - "Name": "", - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "provisioned_concurrency_config": { - "AllocatedProvisionedConcurrentExecutions": 1, - "AvailableProvisionedConcurrentExecutions": 1, - "LastModified": "date", - "RequestedProvisionedConcurrentExecutions": 1, - "Status": "READY", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": { - "recorded-date": "09-04-2024, 07:26:03", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnAllowInvokeLambdaPermissionsStacktopicF723B1A748672DB5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Permission", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fntopic09ED913A", - "PhysicalResourceId": "arn::sns::111111111111::", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Subscription", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "topic69831491", - "PhysicalResourceId": "arn::sns::111111111111:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SNS::Topic", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_topic_attributes_result": { - "Attributes": { - "DisplayName": "", - "EffectiveDeliveryPolicy": { - "http": { - "defaultHealthyRetryPolicy": { - "minDelayTarget": 20, - "maxDelayTarget": 20, - "numRetries": 3, - "numMaxDelayRetries": 0, - "numNoDelayRetries": 0, - "numMinDelayRetries": 0, - "backoffFunction": "linear" - }, - "disableSubscriptionOverrides": false, - "defaultRequestPolicy": { - "headerContentType": "text/plain; charset=UTF-8" - } - } - }, - "Owner": "111111111111", - "Policy": { - "Version": "2008-10-17", - "Id": "__default_policy_ID", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "SNS:GetTopicAttributes", - "SNS:SetTopicAttributes", - "SNS:AddPermission", - "SNS:RemovePermission", - "SNS:DeleteTopic", - "SNS:Subscribe", - "SNS:ListSubscriptionsByTopic", - "SNS:Publish" - ], - "Resource": "arn::sns::111111111111:", - "Condition": { - "StringEquals": { - "AWS:SourceOwner": "111111111111" - } - } - } - ] - }, - "SubscriptionsConfirmed": "0", - "SubscriptionsDeleted": "0", - "SubscriptionsPending": "0", - "TopicArn": "arn::sns::111111111111:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_policy_result": { - "Policy": { - "Version": "2012-10-17", - "Id": "default", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Principal": { - "Service": "sns.amazonaws.com" - }, - "Action": "lambda:InvokeFunction", - "Resource": "arn::lambda::111111111111:function:", - "Condition": { - "ArnLike": { - "AWS:SourceArn": "arn::sns::111111111111:" - } - } - } - ] - }, - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": { - "recorded-date": "30-10-2024, 14:48:16", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRoleDefaultPolicy0ED5D3E5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnSqsEventSourceLambdaSqsSourceStackq2097017B53C3FF8C", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::EventSourceMapping", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "q14836DC8", - "PhysicalResourceId": "https://sqs..amazonaws.com/111111111111/", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SQS::Queue", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "sqs:ReceiveMessage", - "sqs:ChangeMessageVisibility", - "sqs:GetQueueUrl", - "sqs:DeleteMessage", - "sqs:GetQueueAttributes" - ], - "Effect": "Allow", - "Resource": "arn::sqs::111111111111:" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "fnServiceRoleDefaultPolicy0ED5D3E5", - "RoleName": "", - "ResponseMetadata": "" - } - ] - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_esm_result": { - "BatchSize": 1, - "EventSourceArn": "arn::sqs::111111111111:", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "MaximumBatchingWindowInSeconds": 0, - "State": "Enabled", - "StateTransitionReason": "USER_INITIATED", - "UUID": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_queue_atts_result": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "CreatedTimestamp": "timestamp", - "DelaySeconds": "0", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_role_result": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_attached_role_policies_result": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "PolicyName": "AWSLambdaBasicExecutionRole" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_inline_role_policies_result": { - "IsTruncated": false, - "PolicyNames": [ - "fnServiceRoleDefaultPolicy0ED5D3E5" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_code_signing_config": { - "recorded-date": "09-04-2024, 07:19:51", - "recorded-content": { - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "CodeSigningConfig", - "PhysicalResourceId": "arn::lambda::111111111111:code-signing-config:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::CodeSigningConfig", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "config": { - "CodeSigningConfig": { - "AllowedPublishers": { - "SigningProfileVersionArns": [ - "arn::signer::111111111111:/signing-profiles/test" - ] - }, - "CodeSigningConfigArn": "arn::lambda::111111111111:code-signing-config:", - "CodeSigningConfigId": "", - "CodeSigningPolicies": { - "UntrustedArtifactOnDeployment": "Enforce" - }, - "Description": "Code Signing", - "LastModified": "date" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_event_invoke_config": { - "recorded-date": "09-04-2024, 07:20:36", - "recorded-content": { - "event_invoke_config": { - "DestinationConfig": { - "OnFailure": {}, - "OnSuccess": {} - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "LastModified": "datetime", - "MaximumEventAgeInSeconds": 300, - "MaximumRetryAttempts": 1, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version": { - "recorded-date": "07-05-2025, 13:19:10", - "recorded-content": { - "invoke_result": { - "ExecutedVersion": "1", - "Payload": { - "function_version": "1" - }, - "StatusCode": 200, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnVersion7BF8AE5A", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "versions_by_fn": { - "Versions": [ - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function::$LATEST", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function_version": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": { - "recorded-date": "12-10-2024, 10:46:17", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnDynamoDBEventSourceLambdaDynamodbSourceStacktable153BBA79064FDF1D", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::EventSourceMapping", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRoleDefaultPolicy0ED5D3E5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "table8235A42E", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::DynamoDB::Table", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": "dynamodb:ListStreams", - "Effect": "Allow", - "Resource": "*" - }, - { - "Action": [ - "dynamodb:DescribeStream", - "dynamodb:GetRecords", - "dynamodb:GetShardIterator" - ], - "Effect": "Allow", - "Resource": "arn::dynamodb::111111111111:table//stream/" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "fnServiceRoleDefaultPolicy0ED5D3E5", - "RoleName": "", - "ResponseMetadata": "" - } - ] - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_esm_result": { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::dynamodb::111111111111:table//stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 0, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_table_result": { - "Table": { - "AttributeDefinitions": [ - { - "AttributeName": "id", - "AttributeType": "S" - } - ], - "CreationDateTime": "datetime", - "DeletionProtectionEnabled": false, - "ItemCount": 0, - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "LatestStreamArn": "arn::dynamodb::111111111111:table//stream/", - "LatestStreamLabel": "", - "ProvisionedThroughput": { - "NumberOfDecreasesToday": 0, - "ReadCapacityUnits": 5, - "WriteCapacityUnits": 5 - }, - "StreamSpecification": { - "StreamEnabled": true, - "StreamViewType": "NEW_AND_OLD_IMAGES" - }, - "TableArn": "arn::dynamodb::111111111111:table/", - "TableId": "", - "TableName": "", - "TableSizeBytes": 0, - "TableStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stream_result": { - "StreamDescription": { - "CreationRequestDateTime": "datetime", - "KeySchema": [ - { - "AttributeName": "id", - "KeyType": "HASH" - } - ], - "Shards": [ - { - "SequenceNumberRange": { - "StartingSequenceNumber": "starting-sequence-number" - }, - "ShardId": "shard-id" - } - ], - "StreamArn": "arn::dynamodb::111111111111:table//stream/", - "StreamLabel": "", - "StreamStatus": "ENABLED", - "StreamViewType": "NEW_AND_OLD_IMAGES", - "TableName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_role_result": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_attached_role_policies_result": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "PolicyName": "AWSLambdaBasicExecutionRole" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_inline_role_policies_result": { - "IsTruncated": false, - "PolicyNames": [ - "fnServiceRoleDefaultPolicy0ED5D3E5" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": { - "recorded-date": "12-10-2024, 10:52:28", - "recorded-content": { - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnKinesisEventSourceLambdaKinesisSourceStackstream996A3395ED86A30E", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::EventSourceMapping", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRoleDefaultPolicy0ED5D3E5", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Policy", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "stream19075594", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Kinesis::Stream", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "role_policies": { - "policies": [ - { - "PolicyDocument": { - "Statement": [ - { - "Action": [ - "kinesis:DescribeStreamSummary", - "kinesis:GetRecords", - "kinesis:GetShardIterator", - "kinesis:ListShards", - "kinesis:SubscribeToShard", - "kinesis:DescribeStream", - "kinesis:ListStreams" - ], - "Effect": "Allow", - "Resource": "arn::kinesis::111111111111:stream/" - }, - { - "Action": "kinesis:DescribeStream", - "Effect": "Allow", - "Resource": "arn::kinesis::111111111111:stream/" - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "fnServiceRoleDefaultPolicy0ED5D3E5", - "RoleName": "", - "ResponseMetadata": "" - } - ] - }, - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.9", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "fn5FF616E3", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_esm_result": { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::kinesis::111111111111:stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 10, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_stream_result": { - "StreamDescription": { - "EncryptionType": "NONE", - "EnhancedMonitoring": [ - { - "ShardLevelMetrics": [] - } - ], - "HasMoreShards": false, - "RetentionPeriodHours": 24, - "Shards": [ - { - "HashKeyRange": { - "EndingHashKey": "ending_hash", - "StartingHashKey": "starting_hash" - }, - "SequenceNumberRange": { - "StartingSequenceNumber": "starting-sequence-number" - }, - "ShardId": "shard-id" - } - ], - "StreamARN": "arn::kinesis::111111111111:stream/", - "StreamCreationTimestamp": "timestamp", - "StreamModeDetails": { - "StreamMode": "PROVISIONED" - }, - "StreamName": "", - "StreamStatus": "ACTIVE" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_role_result": { - "Role": { - "Arn": "arn::iam::111111111111:role/", - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "CreateDate": "datetime", - "Description": "", - "MaxSessionDuration": 3600, - "Path": "/", - "RoleId": "", - "RoleLastUsed": {}, - "RoleName": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_attached_role_policies_result": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "PolicyName": "AWSLambdaBasicExecutionRole" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_inline_role_policies_result": { - "IsTruncated": false, - "PolicyNames": [ - "fnServiceRoleDefaultPolicy0ED5D3E5" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": { - "recorded-date": "09-04-2024, 07:25:05", - "recorded-content": { - "policy": { - "Policy": { - "Id": "default", - "Statement": [ - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Resource": "arn::lambda::111111111111:function:", - "Sid": "" - }, - { - "Action": "lambda:InvokeFunction", - "Effect": "Allow", - "Principal": { - "Service": "states.amazonaws.com" - }, - "Resource": "arn::lambda::111111111111:function:", - "Sid": "" - } - ], - "Version": "2012-10-17" - }, - "RevisionId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": { - "recorded-date": "09-04-2024, 07:39:50", - "recorded-content": { - "failed-async-lambda": { - "Messages": [ - { - "Body": {}, - "MD5OfBody": "99914b932bd37a50b983c5e7c90ae93b", - "MessageId": "", - "ReceiptHandle": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": { - "recorded-date": "12-10-2024, 10:42:00", - "recorded-content": { - "source_mappings": { - "EventSourceMappings": [ - { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::dynamodb::111111111111:table//stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FilterCriteria": { - "Filters": [ - { - "Pattern": { - "eventName": [ - "DELETE" - ] - } - } - ] - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 0, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "updated_source_mappings": { - "EventSourceMappings": [ - { - "BatchSize": 1, - "BisectBatchOnFunctionError": false, - "DestinationConfig": { - "OnFailure": {} - }, - "EventSourceArn": "arn::dynamodb::111111111111:table//stream/", - "EventSourceMappingArn": "arn::lambda::111111111111:event-source-mapping:", - "FilterCriteria": { - "Filters": [ - { - "Pattern": { - "eventName": [ - "MODIFY" - ] - } - } - ] - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionResponseTypes": [], - "LastModified": "datetime", - "LastProcessingResult": "No records processed", - "MaximumBatchingWindowInSeconds": 0, - "MaximumRecordAgeInSeconds": -1, - "MaximumRetryAttempts": -1, - "ParallelizationFactor": 1, - "StartingPosition": "TRIM_HORIZON", - "State": "Enabled", - "StateTransitionReason": "User action", - "TumblingWindowInSeconds": 0, - "UUID": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_function_tags": { - "recorded-date": "01-10-2024, 12:52:51", - "recorded-content": { - "get_function_result": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.11", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "Environment": "", - "aws:cloudformation:logical-id": "TestFunction", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "lambda:createdBy": "SAM" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_layer_crud": { - "recorded-date": "20-12-2024, 18:23:31", - "recorded-content": { - "layer-name": "", - "cfn-output": { - "LambdaArn": "arn::lambda::111111111111:function:", - "LambdaName": "", - "LayerVersionArn": "arn::lambda::111111111111:layer::1", - "LayerVersionRef": "arn::lambda::111111111111:layer::1" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_logging_config": { - "recorded-date": "08-04-2025, 12:10:56", - "recorded-content": { - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "logical-resource-id", - "PhysicalResourceId": "physical-resource-id", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "logical-resource-id", - "PhysicalResourceId": "physical-resource-id", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "logical-resource-id", - "PhysicalResourceId": "physical-resource-id", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "logging_config": { - "ApplicationLogLevel": "INFO", - "LogFormat": "JSON", - "LogGroup": "/aws/lambda/", - "SystemLogLevel": "INFO" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": { - "recorded-date": "07-05-2025, 13:23:25", - "recorded-content": { - "invoke_result": { - "ExecutedVersion": "1", - "Payload": { - "initialization_type": "provisioned-concurrency" - }, - "StatusCode": 200, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "stack_resources": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fn5FF616E3", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Function", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnServiceRole5D180AFD", - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::IAM::Role", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - }, - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "fnVersion7BF8AE5A", - "PhysicalResourceId": "arn::lambda::111111111111:function:", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::Lambda::Version", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "versions_by_fn": { - "Versions": [ - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function::$LATEST", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function_version": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "", - "CodeSize": "", - "Description": "test description", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.12", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "1" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "provisioned_concurrency_config": { - "AllocatedProvisionedConcurrentExecutions": 1, - "AvailableProvisionedConcurrentExecutions": 1, - "LastModified": "date", - "RequestedProvisionedConcurrentExecutions": 1, - "Status": "READY", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.validation.json deleted file mode 100644 index 759e47d6a6561..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.validation.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaDestinations::test_generic_destination_routing[sqs-sqs]": { - "last_validated_date": "2024-12-10T16:48:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_dynamodb_source": { - "last_validated_date": "2024-10-12T10:46:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_kinesis_source": { - "last_validated_date": "2024-10-12T10:52:28+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_permissions": { - "last_validated_date": "2024-04-09T07:26:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_cfn_lambda_sqs_source": { - "last_validated_date": "2024-10-30T14:48:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::TestCfnLambdaIntegrations::test_lambda_dynamodb_event_filter": { - "last_validated_date": "2024-04-09T07:31:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_cfn_function_url": { - "last_validated_date": "2024-04-16T08:16:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_event_invoke_config": { - "last_validated_date": "2024-04-09T07:20:36+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_alias": { - "last_validated_date": "2025-05-07T15:39:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_dead_letter_config_async_invocation": { - "last_validated_date": "2024-04-09T07:39:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_cfn_run": { - "last_validated_date": "2024-04-09T07:22:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_code_signing_config": { - "last_validated_date": "2024-04-09T07:19:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_function_tags": { - "last_validated_date": "2024-10-01T12:52:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_layer_crud": { - "last_validated_date": "2024-12-20T18:23:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_logging_config": { - "last_validated_date": "2025-04-08T12:12:01+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version": { - "last_validated_date": "2025-05-07T13:19:10+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_version_provisioned_concurrency": { - "last_validated_date": "2025-05-07T13:23:25+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_lambda_w_dynamodb_event_filter_update": { - "last_validated_date": "2024-12-11T09:03:52+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_multiple_lambda_permissions_for_singlefn": { - "last_validated_date": "2024-04-09T07:25:05+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_python_lambda_code_deployed_via_s3": { - "last_validated_date": "2024-04-09T07:38:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_function": { - "last_validated_date": "2024-11-07T03:16:40+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_function_name": { - "last_validated_date": "2024-11-07T03:10:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_lambda.py::test_update_lambda_permissions": { - "last_validated_date": "2024-04-09T07:23:41+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py deleted file mode 100644 index 75afa2549b354..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py +++ /dev/null @@ -1,60 +0,0 @@ -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_logstream(deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/logs_group_and_stream.yaml" - ) - ) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("LogGroupNameOutput")) - - group_name = stack.outputs["LogGroupNameOutput"] - stream_name = stack.outputs["LogStreamNameOutput"] - - snapshot.match("outputs", stack.outputs) - - streams = aws_client.logs.describe_log_streams( - logGroupName=group_name, logStreamNamePrefix=stream_name - )["logStreams"] - assert aws_client.logs.meta.partition == streams[0]["arn"].split(":")[1] - snapshot.match("describe_log_streams", streams) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..logGroups..logGroupArn", - "$..logGroups..logGroupClass", - "$..logGroups..retentionInDays", - ] -) -def test_cfn_handle_log_group_resource(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/logs_group.yml" - ) - ) - - log_group_prefix = stack.outputs["LogGroupNameOutput"] - - response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_prefix) - snapshot.match("describe_log_groups", response) - snapshot.add_transformer(snapshot.transform.key_value("logGroupName")) - - stack.destroy() - response = aws_client.logs.describe_log_groups(logGroupNamePrefix=log_group_prefix) - assert len(response["logGroups"]) == 0 diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.snapshot.json deleted file mode 100644 index 29964de53c6a8..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.snapshot.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py::test_logstream": { - "recorded-date": "29-07-2022, 13:22:53", - "recorded-content": { - "outputs": { - "LogStreamNameOutput": "", - "LogGroupNameOutput": "" - }, - "describe_log_streams": [ - { - "logStreamName": "", - "creationTime": "timestamp", - "arn": "arn::logs::111111111111:log-group::log-stream:", - "storedBytes": 0 - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.py::test_cfn_handle_log_group_resource": { - "recorded-date": "20-06-2024, 16:15:47", - "recorded-content": { - "describe_log_groups": { - "logGroups": [ - { - "arn": "arn::logs::111111111111:log-group::*", - "creationTime": "timestamp", - "logGroupArn": "arn::logs::111111111111:log-group:", - "logGroupClass": "STANDARD", - "logGroupName": "", - "metricFilterCount": 0, - "retentionInDays": 731, - "storedBytes": 0 - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.validation.json deleted file mode 100644 index fce835093de2a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_logs.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/resources/test_logs.py::test_cfn_handle_log_group_resource": { - "last_validated_date": "2024-06-20T16:15:47+00:00" - }, - "tests/aws/services/cloudformation/resources/test_logs.py::test_logstream": { - "last_validated_date": "2022-07-29T11:22:53+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py deleted file mode 100644 index ddb3e81ddb0c9..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py +++ /dev/null @@ -1,95 +0,0 @@ -import os -from operator import itemgetter - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.mark.skip(reason="flaky") -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..ClusterConfig.DedicatedMasterCount", # added in LS - "$..ClusterConfig.DedicatedMasterEnabled", # added in LS - "$..ClusterConfig.DedicatedMasterType", # added in LS - "$..SoftwareUpdateOptions", # missing - "$..OffPeakWindowOptions", # missing - "$..ChangeProgressDetails", # missing - "$..AutoTuneOptions.UseOffPeakWindow", # missing - "$..ClusterConfig.MultiAZWithStandbyEnabled", # missing - "$..AdvancedSecurityOptions.AnonymousAuthEnabled", # missing - # TODO different values: - "$..Processing", - "$..ServiceSoftwareOptions.CurrentVersion", - "$..ClusterConfig.DedicatedMasterEnabled", - "$..ClusterConfig.InstanceType", # TODO the type was set in cfn - "$..AutoTuneOptions.State", - '$..AdvancedOptions."rest.action.multi.allow_explicit_index"', # TODO this was set to false in cfn - ] -) -def test_domain(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("DomainId")) - snapshot.add_transformer(snapshot.transform.key_value("DomainName")) - snapshot.add_transformer(snapshot.transform.key_value("ChangeId")) - snapshot.add_transformer(snapshot.transform.key_value("Endpoint"), priority=-1) - template_path = os.path.join( - os.path.dirname(__file__), "../../../../../templates/opensearch_domain.yml" - ) - result = deploy_cfn_template(template_path=template_path) - domain_endpoint = result.outputs["SearchDomainEndpoint"] - assert domain_endpoint - domain_arn = result.outputs["SearchDomainArn"] - assert domain_arn - domain_name = result.outputs["SearchDomain"] - - domain = aws_client.opensearch.describe_domain(DomainName=domain_name) - assert domain["DomainStatus"] - snapshot.match("describe_domain", domain) - - assert domain_arn == domain["DomainStatus"]["ARN"] - tags_result = aws_client.opensearch.list_tags(ARN=domain_arn) - tags_result["TagList"].sort(key=itemgetter("Key")) - snapshot.match("list_tags", tags_result) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..DomainStatus.AIMLOptions", - "$..DomainStatus.AdvancedSecurityOptions.AnonymousAuthEnabled", - "$..DomainStatus.AutoTuneOptions.State", - "$..DomainStatus.AutoTuneOptions.UseOffPeakWindow", - "$..DomainStatus.ChangeProgressDetails", - "$..DomainStatus.ClusterConfig.MultiAZWithStandbyEnabled", - "$..DomainStatus.ClusterConfig.ZoneAwarenessConfig", - "$..DomainStatus.DomainEndpointOptions.TLSSecurityPolicy", - "$..DomainStatus.IPAddressType", - "$..DomainStatus.IdentityCenterOptions", - "$..DomainStatus.ModifyingProperties", - "$..DomainStatus.OffPeakWindowOptions", - "$..DomainStatus.ServiceSoftwareOptions.CurrentVersion", - "$..DomainStatus.SoftwareUpdateOptions", - ] -) -def test_domain_with_alternative_types(deploy_cfn_template, aws_client, snapshot): - """ - Test that the alternative types for the OpenSearch domain are accepted using the resource documentation example - """ - snapshot.add_transformer(snapshot.transform.key_value("Endpoint")) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/opensearch_domain_alternative_types.yml", - ) - ) - domain_name = stack.outputs["SearchDomain"] - domain = aws_client.opensearch.describe_domain(DomainName=domain_name) - snapshot.match("describe_domain", domain) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.snapshot.json deleted file mode 100644 index fbfd1e1e91e30..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.snapshot.json +++ /dev/null @@ -1,239 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain": { - "recorded-date": "31-08-2023, 17:42:29", - "recorded-content": { - "describe_domain": { - "DomainStatus": { - "ARN": "arn::es::111111111111:domain/", - "AccessPolicies": "", - "AdvancedOptions": { - "override_main_response_version": "false", - "rest.action.multi.allow_explicit_index": "false" - }, - "AdvancedSecurityOptions": { - "AnonymousAuthEnabled": false, - "Enabled": false, - "InternalUserDatabaseEnabled": false - }, - "AutoTuneOptions": { - "State": "ENABLED", - "UseOffPeakWindow": false - }, - "ChangeProgressDetails": { - "ChangeId": "" - }, - "ClusterConfig": { - "ColdStorageOptions": { - "Enabled": false - }, - "DedicatedMasterEnabled": false, - "InstanceCount": 1, - "InstanceType": "r5.large.search", - "MultiAZWithStandbyEnabled": false, - "WarmEnabled": false, - "ZoneAwarenessEnabled": false - }, - "CognitoOptions": { - "Enabled": false - }, - "Created": true, - "Deleted": false, - "DomainEndpointOptions": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "Policy-Min-TLS-1-0-2019-07" - }, - "DomainId": "", - "DomainName": "", - "EBSOptions": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 10, - "VolumeType": "gp2" - }, - "EncryptionAtRestOptions": { - "Enabled": false - }, - "Endpoint": "", - "EngineVersion": "OpenSearch_2.5", - "NodeToNodeEncryptionOptions": { - "Enabled": false - }, - "OffPeakWindowOptions": { - "Enabled": true, - "OffPeakWindow": { - "WindowStartTime": { - "Hours": 2, - "Minutes": 0 - } - } - }, - "Processing": false, - "ServiceSoftwareOptions": { - "AutomatedUpdateDate": "datetime", - "Cancellable": false, - "CurrentVersion": "OpenSearch_2_5_R20230308-P4", - "Description": "There is no software update available for this domain.", - "NewVersion": "", - "OptionalDeployment": true, - "UpdateAvailable": false, - "UpdateStatus": "COMPLETED" - }, - "SnapshotOptions": { - "AutomatedSnapshotStartHour": 0 - }, - "SoftwareUpdateOptions": { - "AutoSoftwareUpdateEnabled": false - }, - "UpgradeProcessing": false - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "list_tags": { - "TagList": [ - { - "Key": "anotherkey", - "Value": "hello" - }, - { - "Key": "foo", - "Value": "bar" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain_with_alternative_types": { - "recorded-date": "08-07-2025, 02:30:47", - "recorded-content": { - "describe_domain": { - "DomainStatus": { - "AIMLOptions": { - "NaturalLanguageQueryGenerationOptions": { - "CurrentState": "NOT_ENABLED", - "DesiredState": "DISABLED" - } - }, - "ARN": "arn::es::111111111111:domain/test-opensearch-domain", - "AccessPolicies": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn::iam::111111111111:root" - }, - "Action": "es:*", - "Resource": "arn::es::111111111111:domain/test-opensearch-domain/*" - } - ] - }, - "AdvancedOptions": { - "override_main_response_version": "true", - "rest.action.multi.allow_explicit_index": "true" - }, - "AdvancedSecurityOptions": { - "AnonymousAuthEnabled": false, - "Enabled": false, - "InternalUserDatabaseEnabled": false - }, - "AutoTuneOptions": { - "State": "DISABLED", - "UseOffPeakWindow": false - }, - "ChangeProgressDetails": { - "ChangeId": "", - "ConfigChangeStatus": "Completed", - "InitiatedBy": "CUSTOMER", - "LastUpdatedTime": "datetime", - "StartTime": "datetime" - }, - "ClusterConfig": { - "ColdStorageOptions": { - "Enabled": false - }, - "DedicatedMasterCount": 3, - "DedicatedMasterEnabled": true, - "DedicatedMasterType": "t3.small.search", - "InstanceCount": 2, - "InstanceType": "t3.small.search", - "MultiAZWithStandbyEnabled": false, - "WarmEnabled": false, - "ZoneAwarenessConfig": { - "AvailabilityZoneCount": 2 - }, - "ZoneAwarenessEnabled": true - }, - "CognitoOptions": { - "Enabled": false - }, - "Created": true, - "Deleted": false, - "DomainEndpointOptions": { - "CustomEndpointEnabled": false, - "EnforceHTTPS": false, - "TLSSecurityPolicy": "Policy-Min-TLS-1-2-2019-07" - }, - "DomainId": "111111111111/test-opensearch-domain", - "DomainName": "test-opensearch-domain", - "DomainProcessingStatus": "Active", - "EBSOptions": { - "EBSEnabled": true, - "Iops": 0, - "VolumeSize": 20, - "VolumeType": "gp2" - }, - "EncryptionAtRestOptions": { - "Enabled": false - }, - "Endpoint": "", - "EngineVersion": "OpenSearch_1.0", - "IPAddressType": "ipv4", - "IdentityCenterOptions": {}, - "ModifyingProperties": [], - "NodeToNodeEncryptionOptions": { - "Enabled": false - }, - "OffPeakWindowOptions": { - "Enabled": true, - "OffPeakWindow": { - "WindowStartTime": { - "Hours": 2, - "Minutes": 0 - } - } - }, - "Processing": false, - "ServiceSoftwareOptions": { - "AutomatedUpdateDate": "datetime", - "Cancellable": false, - "CurrentVersion": "OpenSearch_1_0_R20250625", - "Description": "There is no software update available for this domain.", - "NewVersion": "", - "OptionalDeployment": true, - "UpdateAvailable": false, - "UpdateStatus": "COMPLETED" - }, - "SnapshotOptions": { - "AutomatedSnapshotStartHour": 0 - }, - "SoftwareUpdateOptions": { - "AutoSoftwareUpdateEnabled": false - }, - "UpgradeProcessing": false - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.validation.json deleted file mode 100644 index 1769b2a88f224..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain": { - "last_validated_date": "2023-08-31T15:42:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_opensearch.py::test_domain_with_alternative_types": { - "last_validated_date": "2023-10-05T09:07:39+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py deleted file mode 100644 index b0c4f0b91b6a3..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py +++ /dev/null @@ -1,28 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -# only runs in Docker when run against Pro (since it needs postgres on the system) -@markers.only_in_docker -@markers.aws.validated -def test_redshift_cluster(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/cfn_redshift.yaml" - ) - ) - - # very basic test to check the cluster deploys - assert stack.outputs["ClusterRef"] - assert stack.outputs["ClusterAttEndpointPort"] - assert stack.outputs["ClusterAttEndpointAddress"] diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.validation.json deleted file mode 100644 index 69f04be2accfe..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_redshift.py::test_redshift_cluster": { - "last_validated_date": "2024-02-28T12:42:35+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py deleted file mode 100644 index db32df5683969..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py +++ /dev/null @@ -1,25 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Group.Description", "$..Group.GroupArn"]) -def test_group_defaults(aws_client, deploy_cfn_template, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/resource_group_defaults.yml" - ), - ) - - resource_group = aws_client.resource_groups.get_group(GroupName=stack.outputs["ResourceGroup"]) - snapshot.match("resource-group", resource_group) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.snapshot.json deleted file mode 100644 index a3f11aeabdeed..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.snapshot.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py::test_group_defaults": { - "recorded-date": "16-07-2024, 15:15:11", - "recorded-content": { - "resource-group": { - "Group": { - "GroupArn": "arn::resource-groups::111111111111:group/testgroup", - "Name": "testgroup" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.validation.json deleted file mode 100644 index 33b1cf0308598..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_resource_groups.py::test_group_defaults": { - "last_validated_date": "2024-07-16T15:15:11+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py deleted file mode 100644 index 06cc700e4b077..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py +++ /dev/null @@ -1,76 +0,0 @@ -import os - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_create_record_set_via_id(route53_hosted_zone, deploy_cfn_template): - create_zone_response = route53_hosted_zone() - hosted_zone_id = create_zone_response["HostedZone"]["Id"] - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/route53_hostedzoneid_template.yaml" - ), - parameters=parameters, - max_wait=300, - ) - - -@markers.aws.validated -def test_create_record_set_via_name(deploy_cfn_template, route53_hosted_zone): - create_zone_response = route53_hosted_zone() - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneName": route53_name, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/route53_hostedzonename_template.yaml", - ), - parameters=parameters, - ) - - -@markers.aws.validated -def test_create_record_set_without_resource_record(deploy_cfn_template, route53_hosted_zone): - create_zone_response = route53_hosted_zone() - hosted_zone_id = create_zone_response["HostedZone"]["Id"] - route53_name = create_zone_response["HostedZone"]["Name"] - parameters = {"HostedZoneId": hosted_zone_id, "Name": route53_name} - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/route53_recordset_without_resource_records.yaml", - ), - parameters=parameters, - ) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..HealthCheckConfig.EnableSNI", "$..HealthCheckVersion"] -) -def test_create_health_check(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/route53_healthcheck.yml", - ), - ) - health_check_id = stack.outputs["HealthCheckId"] - print(health_check_id) - health_check = aws_client.route53.get_health_check(HealthCheckId=health_check_id) - - snapshot.add_transformer(snapshot.transform.key_value("Id", "id")) - snapshot.add_transformer(snapshot.transform.key_value("CallerReference", "caller-reference")) - snapshot.match("HealthCheck", health_check["HealthCheck"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.snapshot.json deleted file mode 100644 index 46eb1e650d88c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.snapshot.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_health_check": { - "recorded-date": "22-09-2023, 13:50:49", - "recorded-content": { - "HealthCheck": { - "CallerReference": "", - "HealthCheckConfig": { - "Disabled": false, - "EnableSNI": false, - "FailureThreshold": 3, - "FullyQualifiedDomainName": "localstacktest.com", - "IPAddress": "1.1.1.1", - "Inverted": false, - "MeasureLatency": false, - "Port": 80, - "RequestInterval": 30, - "ResourcePath": "/health", - "Type": "HTTP" - }, - "HealthCheckVersion": 1, - "Id": "" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.validation.json deleted file mode 100644 index 856faff5c112c..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_route53.py::test_create_health_check": { - "last_validated_date": "2023-09-22T11:50:49+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py deleted file mode 100644 index da1be1a4a16d2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py +++ /dev/null @@ -1,155 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_bucketpolicy(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("BucketName")) - bucket_name = f"ls-bucket-{short_uid()}" - snapshot.match("bucket", {"BucketName": bucket_name}) - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucketpolicy.yaml" - ), - parameters={"BucketName": bucket_name}, - template_mapping={"include_policy": True}, - ) - response = aws_client.s3.get_bucket_policy(Bucket=bucket_name)["Policy"] - snapshot.match("get-policy-true", response) - - deploy_cfn_template( - is_update=True, - stack_name=deploy_result.stack_id, - parameters={"BucketName": bucket_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucketpolicy.yaml" - ), - template_mapping={"include_policy": False}, - ) - with pytest.raises(ClientError) as err: - aws_client.s3.get_bucket_policy(Bucket=bucket_name) - snapshot.match("no-policy", err.value.response) - - -@markers.aws.validated -def test_bucket_autoname(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucket_autoname.yaml" - ) - ) - descr_response = aws_client.cloudformation.describe_stacks(StackName=result.stack_id) - output = descr_response["Stacks"][0]["Outputs"][0] - assert output["OutputKey"] == "BucketNameOutput" - assert result.stack_name.lower() in output["OutputValue"] - - -@markers.aws.validated -def test_bucket_versioning(deploy_cfn_template, aws_client): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_versioned_bucket.yaml" - ) - ) - assert "BucketName" in result.outputs - bucket_name = result.outputs["BucketName"] - bucket_version = aws_client.s3.get_bucket_versioning(Bucket=bucket_name) - assert bucket_version["Status"] == "Enabled" - - -@markers.aws.validated -def test_website_configuration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.s3_api()) - - bucket_name_generated = f"ls-bucket-{short_uid()}" - - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_bucket_website_config.yaml" - ), - parameters={"BucketName": bucket_name_generated}, - ) - - bucket_name = result.outputs["BucketNameOutput"] - assert bucket_name_generated == bucket_name - website_url = result.outputs["WebsiteURL"] - assert website_url.startswith(f"http://{bucket_name}.s3-website") - response = aws_client.s3.get_bucket_website(Bucket=bucket_name) - - snapshot.match("get_bucket_website", response) - - -@markers.aws.validated -def test_cors_configuration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.s3_api()) - - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_cors_bucket.yaml" - ), - ) - bucket_name_optional = result.outputs["BucketNameAllParameters"] - cors_info = aws_client.s3.get_bucket_cors(Bucket=bucket_name_optional) - snapshot.match("cors-info-optional", cors_info) - - bucket_name_required = result.outputs["BucketNameOnlyRequired"] - cors_info = aws_client.s3.get_bucket_cors(Bucket=bucket_name_required) - snapshot.match("cors-info-only-required", cors_info) - - -@markers.aws.validated -def test_object_lock_configuration(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.s3_api()) - - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_object_lock_config.yaml" - ), - ) - bucket_name_optional = result.outputs["LockConfigAllParameters"] - cors_info = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name_optional) - snapshot.match("object-lock-info-with-configuration", cors_info) - - bucket_name_required = result.outputs["LockConfigOnlyRequired"] - cors_info = aws_client.s3.get_object_lock_configuration(Bucket=bucket_name_required) - snapshot.match("object-lock-info-only-enabled", cors_info) - - -@markers.aws.validated -def test_cfn_handle_s3_notification_configuration( - aws_client, - deploy_cfn_template, - snapshot, -): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/s3_notification_sqs.yml" - ), - ) - rs = aws_client.s3.get_bucket_notification_configuration(Bucket=stack.outputs["BucketName"]) - snapshot.match("get_bucket_notification_configuration", rs) - - stack.destroy() - - with pytest.raises(ClientError) as ctx: - aws_client.s3.get_bucket_notification_configuration(Bucket=stack.outputs["BucketName"]) - snapshot.match("get_bucket_notification_configuration_error", ctx.value.response) - - snapshot.add_transformer(snapshot.transform.key_value("Id")) - snapshot.add_transformer(snapshot.transform.key_value("QueueArn")) - snapshot.add_transformer(snapshot.transform.key_value("BucketName")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.snapshot.json deleted file mode 100644 index de27f0ba24420..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.snapshot.json +++ /dev/null @@ -1,175 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cors_configuration": { - "recorded-date": "20-04-2023, 20:17:17", - "recorded-content": { - "cors-info-optional": { - "CORSRules": [ - { - "AllowedHeaders": [ - "*", - "x-amz-*" - ], - "AllowedMethods": [ - "GET" - ], - "AllowedOrigins": [ - "*" - ], - "ExposeHeaders": [ - "Date" - ], - "ID": "test-cors-id", - "MaxAgeSeconds": 3600 - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "cors-info-only-required": { - "CORSRules": [ - { - "AllowedMethods": [ - "GET" - ], - "AllowedOrigins": [ - "*" - ] - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_website_configuration": { - "recorded-date": "02-06-2023, 18:24:39", - "recorded-content": { - "get_bucket_website": { - "ErrorDocument": { - "Key": "error.html" - }, - "IndexDocument": { - "Suffix": "index.html" - }, - "RoutingRules": [ - { - "Condition": { - "HttpErrorCodeReturnedEquals": "404", - "KeyPrefixEquals": "out1/" - }, - "Redirect": { - "ReplaceKeyWith": "redirected.html" - } - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_object_lock_configuration": { - "recorded-date": "15-01-2024, 02:31:58", - "recorded-content": { - "object-lock-info-with-configuration": { - "ObjectLockConfiguration": { - "ObjectLockEnabled": "Enabled", - "Rule": { - "DefaultRetention": { - "Days": 2, - "Mode": "GOVERNANCE" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "object-lock-info-only-enabled": { - "ObjectLockConfiguration": { - "ObjectLockEnabled": "Enabled" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucketpolicy": { - "recorded-date": "31-05-2024, 13:41:44", - "recorded-content": { - "bucket": { - "BucketName": "" - }, - "get-policy-true": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "s3:GetObject*", - "s3:GetBucket*", - "s3:List*" - ], - "Resource": [ - "arn::s3:::", - "arn::s3:::/*" - ] - } - ] - }, - "no-policy": { - "Error": { - "BucketName": "", - "Code": "NoSuchBucketPolicy", - "Message": "The bucket policy does not exist" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 404 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": { - "recorded-date": "20-06-2024, 16:57:13", - "recorded-content": { - "get_bucket_notification_configuration": { - "QueueConfigurations": [ - { - "Events": [ - "s3:ObjectCreated:*" - ], - "Id": "", - "QueueArn": "" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_bucket_notification_configuration_error": { - "Error": { - "BucketName": "", - "Code": "NoSuchBucket", - "Message": "The specified bucket does not exist" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 404 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.validation.json deleted file mode 100644 index 2b756e7a7e871..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucket_versioning": { - "last_validated_date": "2024-05-31T13:44:37+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_bucketpolicy": { - "last_validated_date": "2024-05-31T13:41:44+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cfn_handle_s3_notification_configuration": { - "last_validated_date": "2024-06-20T16:57:13+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_cors_configuration": { - "last_validated_date": "2023-04-20T18:17:17+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_object_lock_configuration": { - "last_validated_date": "2024-01-15T02:31:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_s3.py::test_website_configuration": { - "last_validated_date": "2023-06-02T16:24:39+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py deleted file mode 100644 index 6c039975b679e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py +++ /dev/null @@ -1,96 +0,0 @@ -import json -import os -import os.path - -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import retry - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_sam_policies(deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.iam_api()) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sam_function-policies.yaml" - ) - ) - role_name = stack.outputs["HelloWorldFunctionIamRoleName"] - - roles = aws_client.iam.list_attached_role_policies(RoleName=role_name) - assert "AmazonSNSFullAccess" in [p["PolicyName"] for p in roles["AttachedPolicies"]] - snapshot.match("list_attached_role_policies", roles) - - -@markers.aws.validated -def test_sam_template(deploy_cfn_template, aws_client): - # deploy template - func_name = f"test-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/template4.yaml" - ), - parameters={"FunctionName": func_name}, - ) - - # run Lambda test invocation - result = aws_client.lambda_.invoke(FunctionName=func_name) - result = json.load(result["Payload"]) - assert result == {"hello": "world"} - - -@markers.aws.validated -def test_sam_sqs_event(deploy_cfn_template, aws_client): - result_key = f"event-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sam_sqs_template.yml" - ), - parameters={"ResultKey": result_key}, - ) - - queue_url = stack.outputs["QueueUrl"] - bucket_name = stack.outputs["BucketName"] - - message_body = "test" - aws_client.sqs.send_message(QueueUrl=queue_url, MessageBody=message_body) - - def get_object(): - return json.loads( - aws_client.s3.get_object(Bucket=bucket_name, Key=result_key)["Body"].read().decode() - )["Records"][0]["body"] - - body = retry(get_object, retries=10, sleep=5.0) - - assert body == message_body - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..tags", "$..Configuration.CodeSha256"]) -def test_cfn_handle_serverless_api_resource(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sam_api.yml" - ), - ) - - response = aws_client.apigateway.get_rest_api(restApiId=stack.outputs["ApiId"]) - snapshot.match("get_rest_api", response) - - response = aws_client.lambda_.get_function(FunctionName=stack.outputs["LambdaFunction"]) - snapshot.match("get_function", response) - - snapshot.add_transformer(snapshot.transform.lambda_api()) - snapshot.add_transformer(snapshot.transform.apigateway_api()) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.snapshot.json deleted file mode 100644 index fe0c314aae224..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.snapshot.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_policies": { - "recorded-date": "11-07-2023, 18:08:53", - "recorded-content": { - "list_attached_role_policies": { - "AttachedPolicies": [ - { - "PolicyArn": "arn::iam::aws:policy/service-role/", - "PolicyName": "" - }, - { - "PolicyArn": "arn::iam::aws:policy/", - "PolicyName": "" - } - ], - "IsTruncated": false, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_cfn_handle_serverless_api_resource": { - "recorded-date": "15-07-2025, 19:33:25", - "recorded-content": { - "get_rest_api": { - "apiKeySource": "HEADER", - "createdDate": "datetime", - "disableExecuteApiEndpoint": false, - "endpointConfiguration": { - "ipAddressType": "ipv4", - "types": [ - "EDGE" - ] - }, - "id": "", - "name": "", - "rootResourceId": "", - "tags": { - "aws:cloudformation:logical-id": "Api", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "" - }, - "version": "1.0", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "get_function": { - "Code": { - "Location": "", - "RepositoryType": "S3" - }, - "Configuration": { - "Architectures": [ - "x86_64" - ], - "CodeSha256": "W479VjWcFpBg+yx255glPq1ZLEq5WjlmjJi7CmxLFio=", - "CodeSize": "", - "Description": "", - "EphemeralStorage": { - "Size": 512 - }, - "FunctionArn": "arn::lambda::111111111111:function:", - "FunctionName": "", - "Handler": "index.handler", - "LastModified": "date", - "LastUpdateStatus": "Successful", - "LoggingConfig": { - "LogFormat": "Text", - "LogGroup": "/aws/lambda/" - }, - "MemorySize": 128, - "PackageType": "Zip", - "RevisionId": "", - "Role": "arn::iam::111111111111:role/", - "Runtime": "python3.11", - "RuntimeVersionConfig": { - "RuntimeVersionArn": "arn::lambda:::runtime:" - }, - "SnapStart": { - "ApplyOn": "None", - "OptimizationStatus": "Off" - }, - "State": "Active", - "Timeout": 3, - "TracingConfig": { - "Mode": "PassThrough" - }, - "Version": "$LATEST" - }, - "Tags": { - "aws:cloudformation:logical-id": "Lambda", - "aws:cloudformation:stack-id": "arn::cloudformation::111111111111:stack//", - "aws:cloudformation:stack-name": "", - "lambda:createdBy": "SAM" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.validation.json deleted file mode 100644 index 3b1eb14e1cce4..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_cfn_handle_serverless_api_resource": { - "last_validated_date": "2025-07-15T19:33:44+00:00", - "durations_in_seconds": { - "setup": 0.46, - "call": 40.88, - "teardown": 19.65, - "total": 60.99 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_policies": { - "last_validated_date": "2023-07-11T16:08:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sam.py::test_sam_sqs_event": { - "last_validated_date": "2024-04-19T19:45:49+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py deleted file mode 100644 index 5388d26b94a29..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py +++ /dev/null @@ -1,115 +0,0 @@ -import json -import os - -import aws_cdk as cdk -import botocore.exceptions -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..VersionIdsToStages"]) -def test_cfn_secretsmanager_gen_secret(deploy_cfn_template, aws_client, snapshot): - secret_name = f"dev/db/pass-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/secretsmanager_secret.yml" - ), - parameters={"SecretName": secret_name}, - ) - - secret = aws_client.secretsmanager.describe_secret(SecretId=secret_name) - snapshot.match("secret", secret) - snapshot.add_transformer(snapshot.transform.regex(rf"{secret_name}-\w+", "")) - snapshot.add_transformer(snapshot.transform.key_value("Name")) - - # assert that secret has been generated and added to the result template JSON - secret_value = aws_client.secretsmanager.get_secret_value(SecretId=secret_name)["SecretString"] - secret_json = json.loads(secret_value) - assert "password" in secret_json - assert len(secret_json["password"]) == 30 - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Tags", "$..VersionIdsToStages"]) -def test_cfn_handle_secretsmanager_secret(deploy_cfn_template, aws_client, snapshot): - secret_name = f"secret-{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/secretsmanager_secret.yml" - ), - parameters={"SecretName": secret_name}, - ) - - rs = aws_client.secretsmanager.describe_secret(SecretId=secret_name) - snapshot.match("secret", rs) - snapshot.add_transformer(snapshot.transform.regex(rf"{secret_name}-\w+", "")) - snapshot.add_transformer(snapshot.transform.key_value("Name")) - - stack.destroy() - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.secretsmanager.describe_secret(SecretId=secret_name) - - snapshot.match("exception", ex.value.response) - - -@markers.aws.validated -@pytest.mark.parametrize("block_public_policy", ["true", "default"]) -def test_cfn_secret_policy(deploy_cfn_template, block_public_policy, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/secretsmanager_secret_policy.yml" - ), - parameters={"BlockPublicPolicy": block_public_policy}, - ) - secret_id = stack.outputs["SecretId"] - - snapshot.match("outputs", stack.outputs) - secret_name = stack.outputs["SecretId"].split(":")[-1] - snapshot.add_transformer(snapshot.transform.regex(secret_name, "")) - - res = aws_client.secretsmanager.get_resource_policy(SecretId=secret_id) - snapshot.match("resource_policy", res) - snapshot.add_transformer(snapshot.transform.key_value("Name", "policy-name")) - - -@pytest.mark.skip(reason="CFNV2:Other") -@markers.aws.validated -def test_cdk_deployment_generates_secret_value_if_no_value_is_provided( - aws_client, snapshot, infrastructure_setup -): - infra = infrastructure_setup(namespace="SecretGeneration") - stack_name = f"SecretGeneration{short_uid()}" - stack = cdk.Stack(infra.cdk_app, stack_name=stack_name) - - secret_name = f"my_secret{short_uid()}" - secret = cdk.aws_secretsmanager.Secret(stack, id=secret_name, secret_name=secret_name) - - cdk.CfnOutput(stack, "SecretName", value=secret.secret_name) - cdk.CfnOutput(stack, "SecretARN", value=secret.secret_arn) - - with infra.provisioner() as prov: - outputs = prov.get_stack_outputs(stack_name=stack_name) - - secret_name = outputs["SecretName"] - secret_arn = outputs["SecretARN"] - - response = aws_client.secretsmanager.get_secret_value(SecretId=secret_name) - - snapshot.add_transformer( - snapshot.transform.key_value("SecretString", reference_replacement=False) - ) - snapshot.add_transformer(snapshot.transform.regex(secret_arn, "")) - snapshot.add_transformer(snapshot.transform.regex(secret_name, "")) - - snapshot.match("generated_key", response) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.snapshot.json deleted file mode 100644 index fcf5840b4d1b7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.snapshot.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": { - "recorded-date": "03-07-2024, 18:51:39", - "recorded-content": { - "outputs": { - "SecretId": "arn::secretsmanager::111111111111:secret:", - "SecretPolicyArn": "arn::secretsmanager::111111111111:secret:" - }, - "resource_policy": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "Name": "", - "ResourcePolicy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn::iam::111111111111:root" - }, - "Action": "secretsmanager:ReplicateSecretToRegions", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": { - "recorded-date": "03-07-2024, 18:52:05", - "recorded-content": { - "outputs": { - "SecretId": "arn::secretsmanager::111111111111:secret:", - "SecretPolicyArn": "arn::secretsmanager::111111111111:secret:" - }, - "resource_policy": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "Name": "", - "ResourcePolicy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "AWS": "arn::iam::111111111111:root" - }, - "Action": "secretsmanager:ReplicateSecretToRegions", - "Resource": "*" - } - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": { - "recorded-date": "23-05-2024, 17:15:31", - "recorded-content": { - "generated_key": { - "ARN": "", - "CreatedDate": "datetime", - "Name": "", - "SecretString": "secret-string", - "VersionId": "", - "VersionStages": [ - "AWSCURRENT" - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": { - "recorded-date": "03-07-2024, 15:39:56", - "recorded-content": { - "secret": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "CreatedDate": "datetime", - "Description": "Aurora Password", - "LastChangedDate": "datetime", - "Name": "", - "Tags": [ - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-63e3fdc5" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "Secret" - }, - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-63e3fdc5/79663e60-3952-11ef-809b-0affeb5ce635" - } - ], - "VersionIdsToStages": { - "2b1f1af7-47ee-aee1-5609-991d4352ae14": [ - "AWSCURRENT" - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": { - "recorded-date": "11-10-2024, 17:00:31", - "recorded-content": { - "secret": { - "ARN": "arn::secretsmanager::111111111111:secret:", - "CreatedDate": "datetime", - "Description": "Aurora Password", - "LastChangedDate": "datetime", - "Name": "", - "Tags": [ - { - "Key": "aws:cloudformation:stack-name", - "Value": "stack-ab33fda4" - }, - { - "Key": "aws:cloudformation:logical-id", - "Value": "Secret" - }, - { - "Key": "aws:cloudformation:stack-id", - "Value": "arn::cloudformation::111111111111:stack/stack-ab33fda4/47ecee80-87f2-11ef-8f16-0a113fcea55f" - } - ], - "VersionIdsToStages": { - "c80fca61-0302-7921-4b9b-c2c16bc6f457": [ - "AWSCURRENT" - ] - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "exception": { - "Error": { - "Code": "ResourceNotFoundException", - "Message": "Secrets Manager can't find the specified secret." - }, - "Message": "Secrets Manager can't find the specified secret.", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.validation.json deleted file mode 100644 index 62afa75a4bedc..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.validation.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cdk_deployment_generates_secret_value_if_no_value_is_provided": { - "last_validated_date": "2024-05-23T17:15:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_handle_secretsmanager_secret": { - "last_validated_date": "2024-10-11T17:00:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[default]": { - "last_validated_date": "2024-08-01T12:22:53+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secret_policy[true]": { - "last_validated_date": "2024-08-01T12:22:32+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_secretsmanager.py::test_cfn_secretsmanager_gen_secret": { - "last_validated_date": "2024-07-03T15:39:56+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py deleted file mode 100644 index 33612757d962d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py +++ /dev/null @@ -1,159 +0,0 @@ -import os.path - -import aws_cdk as cdk -import pytest - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Attributes.DeliveryPolicy", - "$..Attributes.EffectiveDeliveryPolicy", - "$..Attributes.Policy.Statement..Action", # SNS:Receive is added by moto but not returned in AWS - ] -) -def test_sns_topic_fifo_with_deduplication(deploy_cfn_template, aws_client, snapshot): - snapshot.add_transformer(snapshot.transform.key_value("TopicArn")) - topic_name = f"topic-{short_uid()}.fifo" - - deploy_cfn_template( - parameters={"TopicName": topic_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_fifo_dedup.yaml" - ), - ) - - topics = aws_client.sns.list_topics()["Topics"] - topic_arns = [t["TopicArn"] for t in topics] - - filtered_topics = [t for t in topic_arns if topic_name in t] - assert len(filtered_topics) == 1 - - # assert that the topic is properly created as Fifo - topic_attrs = aws_client.sns.get_topic_attributes(TopicArn=filtered_topics[0]) - snapshot.match("get-topic-attrs", topic_attrs) - - -@markers.aws.needs_fixing -def test_sns_topic_fifo_without_suffix_fails(deploy_cfn_template, aws_client): - stack_name = f"stack-{short_uid()}" - topic_name = f"topic-{short_uid()}" - path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/sns_topic_fifo_dedup.yaml", - ) - - with pytest.raises(Exception) as ex: - deploy_cfn_template( - stack_name=stack_name, template_path=path, parameters={"TopicName": topic_name} - ) - assert ex.typename == "StackDeployError" - - stack = aws_client.cloudformation.describe_stacks(StackName=stack_name)["Stacks"][0] - if is_aws_cloud(): - assert stack.get("StackStatus") in ["ROLLBACK_COMPLETED", "ROLLBACK_IN_PROGRESS"] - else: - assert stack.get("StackStatus") == "CREATE_FAILED" - - -@markers.aws.validated -def test_sns_subscription(deploy_cfn_template, aws_client): - topic_name = f"topic-{short_uid()}" - queue_name = f"topic-{short_uid()}" - stack = deploy_cfn_template( - parameters={"TopicName": topic_name, "QueueName": queue_name}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_topic_subscription.yaml" - ), - ) - - topic_arn = stack.outputs["TopicArnOutput"] - assert topic_arn is not None - - subscriptions = aws_client.sns.list_subscriptions_by_topic(TopicArn=topic_arn) - assert len(subscriptions["Subscriptions"]) > 0 - - -@pytest.mark.skip(reason="CFNV2:Engine") -@markers.aws.validated -def test_deploy_stack_with_sns_topic(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/deploy_template_2.yaml" - ), - parameters={"CompanyName": "MyCompany", "MyEmail1": "my@email.com"}, - ) - assert len(stack.outputs) == 3 - - topic_arn = stack.outputs["MyTopic"] - rs = aws_client.sns.list_topics() - - # Topic resource created - topics = [tp for tp in rs["Topics"] if tp["TopicArn"] == topic_arn] - assert len(topics) == 1 - - stack.destroy() - - # assert topic resource removed - rs = aws_client.sns.list_topics() - topics = [tp for tp in rs["Topics"] if tp["TopicArn"] == topic_arn] - assert not topics - - -@markers.aws.validated -def test_update_subscription(snapshot, deploy_cfn_template, aws_client, sqs_queue, sns_topic): - topic_arn = sns_topic["Attributes"]["TopicArn"] - queue_url = sqs_queue - queue_arn = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - - stack = deploy_cfn_template( - parameters={"TopicArn": topic_arn, "QueueArn": queue_arn}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_subscription.yml" - ), - ) - sub_arn = stack.outputs["SubscriptionArn"] - subscription = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) - snapshot.match("subscription-1", subscription) - - deploy_cfn_template( - parameters={"TopicArn": topic_arn, "QueueArn": queue_arn}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sns_subscription_update.yml" - ), - stack_name=stack.stack_name, - is_update=True, - ) - subscription_updated = aws_client.sns.get_subscription_attributes(SubscriptionArn=sub_arn) - snapshot.match("subscription-2", subscription_updated) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - - -@markers.aws.validated -def test_sns_topic_with_attributes(infrastructure_setup, aws_client, snapshot): - infra = infrastructure_setup(namespace="SnsTests") - stack_name = f"stack-{short_uid()}" - stack = cdk.Stack(infra.cdk_app, stack_name=stack_name) - - # Add more configurations here conform they are needed to be tested - topic = cdk.aws_sns.Topic(stack, id="Topic", fifo=True, message_retention_period_in_days=30) - - cdk.CfnOutput(stack, "TopicArn", value=topic.topic_arn) - with infra.provisioner() as prov: - outputs = prov.get_stack_outputs(stack_name=stack_name) - response = aws_client.sns.get_topic_attributes( - TopicArn=outputs["TopicArn"], - ) - snapshot.match("topic-archive-policy", response["Attributes"]["ArchivePolicy"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.snapshot.json deleted file mode 100644 index 274530a669eed..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.snapshot.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": { - "recorded-date": "27-11-2023, 21:27:29", - "recorded-content": { - "get-topic-attrs": { - "Attributes": { - "ContentBasedDeduplication": "true", - "DisplayName": "", - "EffectiveDeliveryPolicy": { - "http": { - "defaultHealthyRetryPolicy": { - "minDelayTarget": 20, - "maxDelayTarget": 20, - "numRetries": 3, - "numMaxDelayRetries": 0, - "numNoDelayRetries": 0, - "numMinDelayRetries": 0, - "backoffFunction": "linear" - }, - "disableSubscriptionOverrides": false, - "defaultRequestPolicy": { - "headerContentType": "text/plain; charset=UTF-8" - } - } - }, - "FifoTopic": "true", - "Owner": "111111111111", - "Policy": { - "Version": "2008-10-17", - "Id": "__default_policy_ID", - "Statement": [ - { - "Sid": "__default_statement_ID", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": [ - "SNS:GetTopicAttributes", - "SNS:SetTopicAttributes", - "SNS:AddPermission", - "SNS:RemovePermission", - "SNS:DeleteTopic", - "SNS:Subscribe", - "SNS:ListSubscriptionsByTopic", - "SNS:Publish" - ], - "Resource": "", - "Condition": { - "StringEquals": { - "AWS:SourceOwner": "111111111111" - } - } - } - ] - }, - "SubscriptionsConfirmed": "0", - "SubscriptionsDeleted": "0", - "SubscriptionsPending": "0", - "TopicArn": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_update_subscription": { - "recorded-date": "29-03-2024, 21:16:26", - "recorded-content": { - "subscription-1": { - "Attributes": { - "ConfirmationWasAuthenticated": "true", - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "PendingConfirmation": "false", - "Protocol": "sqs", - "RawMessageDelivery": "true", - "SubscriptionArn": "arn::sns::111111111111::", - "SubscriptionPrincipal": "arn::iam::111111111111:user/", - "TopicArn": "arn::sns::111111111111:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "subscription-2": { - "Attributes": { - "ConfirmationWasAuthenticated": "true", - "Endpoint": "arn::sqs::111111111111:", - "Owner": "111111111111", - "PendingConfirmation": "false", - "Protocol": "sqs", - "RawMessageDelivery": "false", - "SubscriptionArn": "arn::sns::111111111111::", - "SubscriptionPrincipal": "arn::iam::111111111111:user/", - "TopicArn": "arn::sns::111111111111:" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_with_attributes": { - "recorded-date": "16-08-2024, 15:44:50", - "recorded-content": { - "topic-archive-policy": { - "MessageRetentionPeriod": "30" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.validation.json deleted file mode 100644 index a25c4e80b86b8..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_fifo_with_deduplication": { - "last_validated_date": "2023-11-27T20:27:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_sns_topic_with_attributes": { - "last_validated_date": "2024-08-16T15:44:50+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sns.py::test_update_subscription": { - "last_validated_date": "2024-03-29T21:16:21+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py deleted file mode 100644 index 2599e2bb1f520..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py +++ /dev/null @@ -1,150 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -def test_sqs_queue_policy(deploy_cfn_template, aws_client, snapshot): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_with_queuepolicy.yaml" - ) - ) - queue_url = result.outputs["QueueUrlOutput"] - resp = aws_client.sqs.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]) - snapshot.match("policy", resp) - snapshot.add_transformer(snapshot.transform.key_value("Resource")) - - -@markers.aws.validated -def test_sqs_fifo_queue_generates_valid_name(deploy_cfn_template): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_fifo_autogenerate_name.yaml" - ), - parameters={"IsFifo": "true"}, - max_wait=240, - ) - assert ".fifo" in result.outputs["FooQueueName"] - - -@markers.aws.validated -def test_sqs_non_fifo_queue_generates_valid_name(deploy_cfn_template): - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_fifo_autogenerate_name.yaml" - ), - parameters={"IsFifo": "false"}, - max_wait=240, - ) - assert ".fifo" not in result.outputs["FooQueueName"] - - -@markers.aws.validated -def test_cfn_handle_sqs_resource(deploy_cfn_template, aws_client, snapshot): - queue_name = f"queue-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_fifo_queue.yml" - ), - parameters={"QueueName": queue_name}, - ) - - rs = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueURL"], AttributeNames=["All"] - ) - snapshot.match("queue", rs) - snapshot.add_transformer(snapshot.transform.regex(queue_name, "")) - - # clean up - stack.destroy() - - with pytest.raises(ClientError) as ctx: - aws_client.sqs.get_queue_url(QueueName=f"{queue_name}.fifo") - snapshot.match("error", ctx.value.response) - - -@markers.aws.validated -def test_update_queue_no_change(deploy_cfn_template, aws_client, snapshot): - bucket_name = f"bucket-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_queue_update_no_change.yml" - ), - parameters={ - "AddBucket": "false", - "BucketName": bucket_name, - }, - ) - queue_url = stack.outputs["QueueUrl"] - queue_arn = stack.outputs["QueueArn"] - snapshot.add_transformer(snapshot.transform.regex(queue_url, "")) - snapshot.add_transformer(snapshot.transform.regex(queue_arn, "")) - - snapshot.match("outputs-1", stack.outputs) - - # deploy a second time with no change to the SQS queue - updated_stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_queue_update_no_change.yml" - ), - is_update=True, - stack_name=stack.stack_name, - parameters={ - "AddBucket": "true", - "BucketName": bucket_name, - }, - ) - snapshot.match("outputs-2", updated_stack.outputs) - - -@markers.aws.validated -def test_update_sqs_queuepolicy(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_with_queuepolicy.yaml" - ) - ) - - policy = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - snapshot.match("policy1", policy["Attributes"]["Policy"]) - - updated_stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sqs_with_queuepolicy_updated.yaml" - ), - is_update=True, - stack_name=stack.stack_name, - ) - - def check_policy_updated(): - policy_updated = aws_client.sqs.get_queue_attributes( - QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - assert policy_updated["Attributes"]["Policy"] != policy["Attributes"]["Policy"] - return policy_updated - - wait_until(check_policy_updated) - - policy = aws_client.sqs.get_queue_attributes( - QueueUrl=updated_stack.outputs["QueueUrlOutput"], AttributeNames=["Policy"] - ) - - snapshot.match("policy2", policy["Attributes"]["Policy"]) - snapshot.add_transformer(snapshot.transform.cloudformation_api()) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.snapshot.json deleted file mode 100644 index 860864e9c0b2e..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.snapshot.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_queue_no_change": { - "recorded-date": "08-12-2023, 21:11:26", - "recorded-content": { - "outputs-1": { - "QueueArn": "", - "QueueUrl": "" - }, - "outputs-2": { - "QueueArn": "", - "QueueUrl": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_sqs_queuepolicy": { - "recorded-date": "27-03-2024, 20:30:24", - "recorded-content": { - "policy1": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "arn::sqs::111111111111:" - } - ] - }, - "policy2": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Deny", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "arn::sqs::111111111111:" - } - ] - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_queue_policy": { - "recorded-date": "03-07-2024, 19:49:04", - "recorded-content": { - "policy": { - "Attributes": { - "Policy": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": [ - "sqs:SendMessage", - "sqs:GetQueueAttributes", - "sqs:GetQueueUrl" - ], - "Resource": "" - } - ] - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_cfn_handle_sqs_resource": { - "recorded-date": "03-07-2024, 20:03:51", - "recorded-content": { - "queue": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "ContentBasedDeduplication": "false", - "CreatedTimestamp": "timestamp", - "DeduplicationScope": "queue", - "DelaySeconds": "0", - "FifoQueue": "true", - "FifoThroughputLimit": "perQueue", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:.fifo", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "error": { - "Error": { - "Code": "AWS.SimpleQueueService.NonExistentQueue", - "Message": "The specified queue does not exist.", - "QueryErrorCode": "QueueDoesNotExist", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.validation.json deleted file mode 100644 index 18d7ae6c4fd05..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_cfn_handle_sqs_resource": { - "last_validated_date": "2024-07-03T20:03:51+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_fifo_queue_generates_valid_name": { - "last_validated_date": "2024-05-15T02:01:00+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_non_fifo_queue_generates_valid_name": { - "last_validated_date": "2024-05-15T01:59:34+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_sqs_queue_policy": { - "last_validated_date": "2024-07-03T19:49:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_queue_no_change": { - "last_validated_date": "2023-12-08T20:11:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_sqs.py::test_update_sqs_queuepolicy": { - "last_validated_date": "2024-03-27T20:30:23+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py deleted file mode 100644 index 7bf9ff53445f1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py +++ /dev/null @@ -1,165 +0,0 @@ -import os.path - -import botocore.exceptions -import pytest -from localstack_snapshot.snapshots.transformer import SortingTransformer - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.utils.common import short_uid - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify(paths=["$..Error.Message", "$..message"]) -def test_parameter_defaults(deploy_cfn_template, aws_client, snapshot): - ssm_parameter_value = f"custom-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ), - parameters={"Input": ssm_parameter_value}, - ) - - parameter_name = stack.outputs["CustomParameterOutput"] - param = aws_client.ssm.get_parameter(Name=parameter_name) - snapshot.match("ssm_parameter", param) - snapshot.add_transformer(snapshot.transform.key_value("Name")) - snapshot.add_transformer(snapshot.transform.key_value("Value")) - - stack.destroy() - - with pytest.raises(botocore.exceptions.ClientError) as ctx: - aws_client.ssm.get_parameter(Name=parameter_name) - snapshot.match("ssm_parameter_not_found", ctx.value.response) - - -@markers.aws.validated -def test_update_ssm_parameters(deploy_cfn_template, aws_client): - ssm_parameter_value = f"custom-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ), - parameters={"Input": ssm_parameter_value}, - ) - - ssm_parameter_value = f"new-custom-{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_parameter_defaultname.yaml" - ), - parameters={"Input": ssm_parameter_value}, - ) - - parameter_name = stack.outputs["CustomParameterOutput"] - param = aws_client.ssm.get_parameter(Name=parameter_name) - assert param["Parameter"]["Value"] == ssm_parameter_value - - -@markers.aws.validated -def test_update_ssm_parameter_tag(deploy_cfn_template, aws_client): - ssm_parameter_value = f"custom-{short_uid()}" - tag_value = f"tag-{short_uid()}" - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/ssm_parameter_defaultname_withtags.yaml", - ), - parameters={ - "Input": ssm_parameter_value, - "TagValue": tag_value, - }, - ) - parameter_name = stack.outputs["CustomParameterOutput"] - ssm_tags = aws_client.ssm.list_tags_for_resource( - ResourceType="Parameter", ResourceId=parameter_name - )["TagList"] - tags_pre_update = {tag["Key"]: tag["Value"] for tag in ssm_tags} - assert tags_pre_update["A"] == tag_value - - tag_value_new = f"tag-{short_uid()}" - deploy_cfn_template( - is_update=True, - stack_name=stack.stack_name, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/ssm_parameter_defaultname_withtags.yaml", - ), - parameters={ - "Input": ssm_parameter_value, - "TagValue": tag_value_new, - }, - ) - - ssm_tags = aws_client.ssm.list_tags_for_resource( - ResourceType="Parameter", ResourceId=parameter_name - )["TagList"] - tags_post_update = {tag["Key"]: tag["Value"] for tag in ssm_tags} - assert tags_post_update["A"] == tag_value_new - - # TODO: re-enable after fixing updates in general - # deploy_cfn_template( - # is_update=True, - # stack_name=stack.stack_name, - # template_path=os.path.join( - # os.path.dirname(__file__), "../../templates/ssm_parameter_defaultname.yaml" - # ), - # parameters={ - # "Input": ssm_parameter_value, - # }, - # ) - # - # ssm_tags = aws_client.ssm.list_tags_for_resource(ResourceType="Parameter", ResourceId=parameter_name)['TagList'] - # assert ssm_tags == [] - - -@pytest.mark.skip(reason="CFNV2:DescribeStackResource") -@markers.snapshot.skip_snapshot_verify(paths=["$..DriftInformation", "$..Metadata"]) -@markers.aws.validated -def test_deploy_patch_baseline(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_patch_baseline.yml" - ), - ) - - describe_resource = aws_client.cloudformation.describe_stack_resource( - StackName=stack.stack_name, LogicalResourceId="myPatchBaseline" - )["StackResourceDetail"] - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer( - snapshot.transform.key_value("PhysicalResourceId", "physical_resource_id") - ) - snapshot.match("patch_baseline", describe_resource) - - -@markers.aws.validated -def test_maintenance_window(deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/ssm_maintenance_window.yml" - ), - ) - - describe_resource = aws_client.cloudformation.describe_stack_resources( - StackName=stack.stack_name - )["StackResources"] - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer( - snapshot.transform.key_value("PhysicalResourceId", "physical_resource_id") - ) - snapshot.add_transformer( - SortingTransformer("MaintenanceWindow", lambda x: x["LogicalResourceId"]), priority=-1 - ) - snapshot.match("MaintenanceWindow", describe_resource) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.snapshot.json deleted file mode 100644 index b20140c4c46e1..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.snapshot.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_deploy_patch_baseline": { - "recorded-date": "05-07-2023, 10:13:24", - "recorded-content": { - "patch_baseline": { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LastUpdatedTimestamp": "timestamp", - "LogicalResourceId": "myPatchBaseline", - "Metadata": {}, - "PhysicalResourceId": "", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::SSM::PatchBaseline", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_maintenance_window": { - "recorded-date": "14-07-2023, 14:06:23", - "recorded-content": { - "MaintenanceWindow": [ - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchBaselineAML", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::PatchBaseline", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchBaselineAML2", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::PatchBaseline", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchServerMaintenanceWindow", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::MaintenanceWindow", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchServerMaintenanceWindowTarget", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::MaintenanceWindowTarget", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - }, - { - "StackName": "", - "StackId": "arn::cloudformation::111111111111:stack//", - "LogicalResourceId": "PatchServerTask", - "PhysicalResourceId": "", - "ResourceType": "AWS::SSM::MaintenanceWindowTask", - "Timestamp": "timestamp", - "ResourceStatus": "CREATE_COMPLETE", - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - } - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_parameter_defaults": { - "recorded-date": "03-07-2024, 20:30:04", - "recorded-content": { - "ssm_parameter": { - "Parameter": { - "ARN": "arn::ssm::111111111111:parameter/", - "DataType": "text", - "LastModifiedDate": "datetime", - "Name": "", - "Type": "String", - "Value": "", - "Version": 1 - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "ssm_parameter_not_found": { - "Error": { - "Code": "ParameterNotFound", - "Message": "" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.validation.json deleted file mode 100644 index 3406bb65e62ee..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.validation.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_deploy_patch_baseline": { - "last_validated_date": "2023-07-05T08:13:24+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_maintenance_window": { - "last_validated_date": "2023-07-14T12:06:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_ssm.py::test_parameter_defaults": { - "last_validated_date": "2024-07-03T20:30:04+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py deleted file mode 100644 index 9d85f36aba3d2..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py +++ /dev/null @@ -1,128 +0,0 @@ -import os - -import pytest -from botocore.exceptions import ClientError - -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.config import SECONDARY_TEST_AWS_ACCOUNT_ID, SECONDARY_TEST_AWS_REGION_NAME -from localstack.testing.pytest import markers -from localstack.utils.files import load_file -from localstack.utils.strings import long_uid, short_uid -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -@pytest.fixture -def wait_stack_set_operation(aws_client): - def waiter(stack_set_name: str, operation_id: str): - def _operation_is_ready(): - operation = aws_client.cloudformation.describe_stack_set_operation( - StackSetName=stack_set_name, - OperationId=operation_id, - ) - return operation["StackSetOperation"]["Status"] not in ["RUNNING", "STOPPING"] - - wait_until(_operation_is_ready) - - return waiter - - -@markers.aws.manual_setup_required -def test_create_stack_set_with_stack_instances( - account_id, - region_name, - aws_client, - snapshot, - wait_stack_set_operation, -): - """ "Account <...> should have 'AWSCloudFormationStackSetAdministrationRole' role with trust relationship to CloudFormation service.""" - snapshot.add_transformer(snapshot.transform.key_value("StackSetId", "stack-set-id")) - - stack_set_name = f"StackSet-{short_uid()}" - - template_body = load_file( - os.path.join(os.path.dirname(__file__), "../../../../../templates/s3_cors_bucket.yaml") - ) - - result = aws_client.cloudformation.create_stack_set( - StackSetName=stack_set_name, - TemplateBody=template_body, - ) - - snapshot.match("create_stack_set", result) - - create_instances_result = aws_client.cloudformation.create_stack_instances( - StackSetName=stack_set_name, - Accounts=[account_id], - Regions=[region_name], - ) - - snapshot.match("create_stack_instances", create_instances_result) - - wait_stack_set_operation(stack_set_name, create_instances_result["OperationId"]) - - # make sure additional calls do not result in errors - # even the stack already exists, but returns operation id instead - create_instances_result = aws_client.cloudformation.create_stack_instances( - StackSetName=stack_set_name, - Accounts=[account_id], - Regions=[region_name], - ) - - assert "OperationId" in create_instances_result - - wait_stack_set_operation(stack_set_name, create_instances_result["OperationId"]) - - delete_instances_result = aws_client.cloudformation.delete_stack_instances( - StackSetName=stack_set_name, - Accounts=[account_id], - Regions=[region_name], - RetainStacks=False, - ) - wait_stack_set_operation(stack_set_name, delete_instances_result["OperationId"]) - - aws_client.cloudformation.delete_stack_set(StackSetName=stack_set_name) - - -@markers.aws.validated -def test_delete_nonexistent_stack_set(aws_client, snapshot): - # idempotent - aws_client.cloudformation.delete_stack_set( - StackSetName="non-existent-stack-set-id", - ) - - bad_stack_set_id = f"foo:{long_uid()}" - snapshot.add_transformer(snapshot.transform.regex(bad_stack_set_id, "")) - - aws_client.cloudformation.delete_stack_set( - StackSetName=bad_stack_set_id, - ) - - -@markers.aws.validated -def test_fetch_non_existent_stack_set_instances(aws_client, snapshot): - with pytest.raises(ClientError) as e: - aws_client.cloudformation.create_stack_instances( - StackSetName="non-existent-stack-set-id", - Accounts=[SECONDARY_TEST_AWS_ACCOUNT_ID], - Regions=[SECONDARY_TEST_AWS_REGION_NAME], - ) - - snapshot.match("non-existent-stack-set-name", e.value) - - bad_stack_set_id = f"foo:{long_uid()}" - snapshot.add_transformer(snapshot.transform.regex(bad_stack_set_id, "")) - - with pytest.raises(ClientError) as e: - aws_client.cloudformation.create_stack_instances( - StackSetName=bad_stack_set_id, - Accounts=[SECONDARY_TEST_AWS_ACCOUNT_ID], - Regions=[SECONDARY_TEST_AWS_REGION_NAME], - ) - - snapshot.match("non-existent-stack-set-id", e.value) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.snapshot.json deleted file mode 100644 index 9ce96422b848a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.snapshot.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": { - "recorded-date": "24-05-2023, 15:32:47", - "recorded-content": { - "create_stack_set": { - "StackSetId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "create_stack_instances": { - "OperationId": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": { - "recorded-date": "25-07-2025, 14:15:09", - "recorded-content": { - "non-existent-stack-set-name": "An error occurred (StackSetNotFoundException) when calling the CreateStackInstances operation: StackSet non-existent-stack-set-id not found", - "non-existent-stack-set-id": "An error occurred (StackSetNotFoundException) when calling the CreateStackInstances operation: StackSet not found" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_delete_nonexistent_stack_set": { - "recorded-date": "25-07-2025, 14:57:22", - "recorded-content": {} - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.validation.json deleted file mode 100644 index 80af31738711a..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_create_stack_set_with_stack_instances": { - "last_validated_date": "2023-05-24T13:32:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_delete_nonexistent_stack_set": { - "last_validated_date": "2025-07-25T14:57:22+00:00", - "durations_in_seconds": { - "setup": 1.04, - "call": 0.34, - "teardown": 0.0, - "total": 1.38 - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stack_sets.py::test_fetch_non_existent_stack_set_instances": { - "last_validated_date": "2025-07-25T14:15:09+00:00", - "durations_in_seconds": { - "setup": 1.7, - "call": 0.6, - "teardown": 0.0, - "total": 2.3 - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py deleted file mode 100644 index e8712f669d537..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py +++ /dev/null @@ -1,388 +0,0 @@ -import json -import os -import urllib.parse - -import pytest -from localstack_snapshot.snapshots.transformer import JsonpathTransformer - -from localstack import config -from localstack.testing.pytest import markers -from localstack.testing.pytest.stepfunctions.utils import await_execution_terminated -from localstack.utils.strings import short_uid -from localstack.utils.sync import wait_until - - -@markers.aws.validated -def test_statemachine_definitionsubstitution(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/stepfunctions_statemachine_substitutions.yaml", - ) - ) - - assert len(stack.outputs) == 1 - statemachine_arn = stack.outputs["StateMachineArnOutput"] - - # execute statemachine - ex_result = aws_client.stepfunctions.start_execution(stateMachineArn=statemachine_arn) - - def _is_executed(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=ex_result["executionArn"])[ - "status" - ] - != "RUNNING" - ) - - wait_until(_is_executed) - execution_desc = aws_client.stepfunctions.describe_execution( - executionArn=ex_result["executionArn"] - ) - assert execution_desc["status"] == "SUCCEEDED" - # sync execution is currently not supported since botocore adds a "sync-" prefix - # ex_result = stepfunctions_client.start_sync_execution(stateMachineArn=statemachine_arn) - - assert "hello from statemachine" in execution_desc["output"] - - -@pytest.mark.skip( - reason="CFNV2:Engine During change set describe the a Ref to a not yet deployed resource returns null which is an invalid input for Fn::Split" -) -@markers.aws.validated -def test_nested_statemachine_with_sync2(deploy_cfn_template, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_nested_sync2.json" - ) - ) - - parent_arn = stack.outputs["ParentStateMachineArnOutput"] - assert parent_arn - - ex_result = aws_client.stepfunctions.start_execution( - stateMachineArn=parent_arn, input='{"Value": 1}' - ) - - def _is_executed(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=ex_result["executionArn"])[ - "status" - ] - != "RUNNING" - ) - - wait_until(_is_executed) - execution_desc = aws_client.stepfunctions.describe_execution( - executionArn=ex_result["executionArn"] - ) - assert execution_desc["status"] == "SUCCEEDED" - output = json.loads(execution_desc["output"]) - assert output["Value"] == 3 - - -@markers.aws.needs_fixing -def test_apigateway_invoke(deploy_cfn_template, aws_client): - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_apigateway.yaml" - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello from stepfunctions" in execution_result["output"] - - -@markers.aws.validated -def test_apigateway_invoke_with_path(deploy_cfn_template, aws_client): - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/sfn_apigateway_two_integrations.yaml", - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello_with_path from stepfunctions" in execution_result["output"] - - -@markers.aws.only_localstack -def test_apigateway_invoke_localhost(deploy_cfn_template, aws_client): - """tests the same as above but with the "generic" localhost version of invoking the apigateway""" - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_apigateway.yaml" - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - api_url = deploy_result.outputs["LsApiEndpointA06D37E8"] - - # instead of changing the template, we're just mapping the endpoint here to the more generic path-based version - state_def = aws_client.stepfunctions.describe_state_machine(stateMachineArn=state_machine_arn)[ - "definition" - ] - parsed = urllib.parse.urlparse(api_url) - api_id = parsed.hostname.split(".")[0] - state = json.loads(state_def) - stage = state["States"]["LsCallApi"]["Parameters"]["Stage"] - state["States"]["LsCallApi"]["Parameters"]["ApiEndpoint"] = ( - f"{config.internal_service_url()}/restapis/{api_id}" - ) - state["States"]["LsCallApi"]["Parameters"]["Stage"] = stage - - aws_client.stepfunctions.update_state_machine( - stateMachineArn=state_machine_arn, definition=json.dumps(state) - ) - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello from stepfunctions" in execution_result["output"] - - -@markers.aws.only_localstack -def test_apigateway_invoke_localhost_with_path(deploy_cfn_template, aws_client): - """tests the same as above but with the "generic" localhost version of invoking the apigateway""" - deploy_result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/sfn_apigateway_two_integrations.yaml", - ) - ) - state_machine_arn = deploy_result.outputs["statemachineOutput"] - api_url = deploy_result.outputs["LsApiEndpointA06D37E8"] - - # instead of changing the template, we're just mapping the endpoint here to the more generic path-based version - state_def = aws_client.stepfunctions.describe_state_machine(stateMachineArn=state_machine_arn)[ - "definition" - ] - parsed = urllib.parse.urlparse(api_url) - api_id = parsed.hostname.split(".")[0] - state = json.loads(state_def) - stage = state["States"]["LsCallApi"]["Parameters"]["Stage"] - state["States"]["LsCallApi"]["Parameters"]["ApiEndpoint"] = ( - f"{config.internal_service_url()}/restapis/{api_id}" - ) - state["States"]["LsCallApi"]["Parameters"]["Stage"] = stage - - aws_client.stepfunctions.update_state_machine( - stateMachineArn=state_machine_arn, definition=json.dumps(state) - ) - - execution_arn = aws_client.stepfunctions.start_execution(stateMachineArn=state_machine_arn)[ - "executionArn" - ] - - def _sfn_finished_running(): - return ( - aws_client.stepfunctions.describe_execution(executionArn=execution_arn)["status"] - != "RUNNING" - ) - - wait_until(_sfn_finished_running) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - assert "hello_with_path from stepfunctions" in execution_result["output"] - - -@pytest.mark.skip("Terminates with FAILED on cloud; convert to SFN v2 snapshot lambda test.") -@markers.aws.needs_fixing -def test_retry_and_catch(deploy_cfn_template, aws_client): - """ - Scenario: - - Lambda invoke (incl. 3 retries) - => catch (Send SQS message with body "Fail") - => next (Send SQS message with body "Success") - - The Lambda function simply raises an Exception, so it will always fail. - It should fail all 4 attempts (1x invoke + 3x retries) which should then trigger the catch path - and send a "Fail" message to the queue. - """ - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../../templates/sfn_retry_catch.yaml" - ) - ) - queue_url = stack.outputs["queueUrlOutput"] - statemachine_arn = stack.outputs["smArnOutput"] - assert statemachine_arn - - execution = aws_client.stepfunctions.start_execution(stateMachineArn=statemachine_arn) - execution_arn = execution["executionArn"] - - await_execution_terminated(aws_client.stepfunctions, execution_arn) - - execution_result = aws_client.stepfunctions.describe_execution(executionArn=execution_arn) - assert execution_result["status"] == "SUCCEEDED" - - receive_result = aws_client.sqs.receive_message(QueueUrl=queue_url, WaitTimeSeconds=5) - assert receive_result["Messages"][0]["Body"] == "Fail" - - -@markers.aws.validated -def test_cfn_statemachine_with_dependencies(deploy_cfn_template, aws_client): - sm_name = f"sm_{short_uid()}" - activity_name = f"act_{short_uid()}" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/statemachine_machine_with_activity.yml", - ), - max_wait=150, - parameters={"StateMachineName": sm_name, "ActivityName": activity_name}, - ) - - rs = aws_client.stepfunctions.list_state_machines() - statemachines = [sm for sm in rs["stateMachines"] if sm_name in sm["name"]] - assert len(statemachines) == 1 - - rs = aws_client.stepfunctions.list_activities() - activities = [act for act in rs["activities"] if activity_name in act["name"]] - assert len(activities) == 1 - - stack.destroy() - - rs = aws_client.stepfunctions.list_state_machines() - statemachines = [sm for sm in rs["stateMachines"] if sm_name in sm["name"]] - - assert not statemachines - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..encryptionConfiguration", "$..tracingConfiguration"] -) -def test_cfn_statemachine_default_s3_location( - s3_create_bucket, deploy_cfn_template, aws_client, sfn_snapshot -): - sfn_snapshot.add_transformers_list( - [ - JsonpathTransformer("$..roleArn", "role-arn"), - JsonpathTransformer("$..stateMachineArn", "state-machine-arn"), - JsonpathTransformer("$..name", "state-machine-name"), - ] - ) - cfn_template_path = os.path.join( - os.path.dirname(__file__), - "../../../../../templates/statemachine_machine_default_s3_location.yml", - ) - - stack_name = f"test-cfn-statemachine-default-s3-location-{short_uid()}" - - file_key = f"file-key-{short_uid()}.json" - bucket_name = s3_create_bucket() - state_machine_template = { - "Comment": "step: on create", - "StartAt": "S0", - "States": {"S0": {"Type": "Succeed"}}, - } - - aws_client.s3.put_object( - Bucket=bucket_name, Key=file_key, Body=json.dumps(state_machine_template) - ) - - stack = deploy_cfn_template( - stack_name=stack_name, - template_path=cfn_template_path, - max_wait=150, - parameters={"BucketName": bucket_name, "ObjectKey": file_key}, - ) - - stack_outputs = stack.outputs - statemachine_arn = stack_outputs["StateMachineArnOutput"] - - describe_state_machine_output_on_create = aws_client.stepfunctions.describe_state_machine( - stateMachineArn=statemachine_arn - ) - sfn_snapshot.match( - "describe_state_machine_output_on_create", describe_state_machine_output_on_create - ) - - file_key = f"2-{file_key}" - state_machine_template["Comment"] = "step: on update" - aws_client.s3.put_object( - Bucket=bucket_name, Key=file_key, Body=json.dumps(state_machine_template) - ) - deploy_cfn_template( - stack_name=stack_name, - template_path=cfn_template_path, - is_update=True, - parameters={"BucketName": bucket_name, "ObjectKey": file_key}, - ) - - describe_state_machine_output_on_update = aws_client.stepfunctions.describe_state_machine( - stateMachineArn=statemachine_arn - ) - sfn_snapshot.match( - "describe_state_machine_output_on_update", describe_state_machine_output_on_update - ) - - -@markers.aws.validated -@markers.snapshot.skip_snapshot_verify( - paths=["$..encryptionConfiguration", "$..tracingConfiguration"] -) -def test_statemachine_create_with_logging_configuration( - deploy_cfn_template, aws_client, sfn_snapshot -): - sfn_snapshot.add_transformers_list( - [ - JsonpathTransformer("$..roleArn", "role-arn"), - JsonpathTransformer("$..stateMachineArn", "state-machine-arn"), - JsonpathTransformer("$..name", "state-machine-name"), - JsonpathTransformer("$..logGroupArn", "log-group-arn"), - ] - ) - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../../templates/statemachine_machine_logging_configuration.yml", - ) - ) - statemachine_arn = stack.outputs["StateMachineArnOutput"] - describe_state_machine_result = aws_client.stepfunctions.describe_state_machine( - stateMachineArn=statemachine_arn - ) - sfn_snapshot.match("describe_state_machine_result", describe_state_machine_result) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.snapshot.json deleted file mode 100644 index d0fc2a3e304de..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.snapshot.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": { - "recorded-date": "17-12-2024, 16:06:46", - "recorded-content": { - "describe_state_machine_output_on_create": { - "creationDate": "datetime", - "definition": { - "Comment": "step: on create", - "StartAt": "S0", - "States": { - "S0": { - "Type": "Succeed" - } - } - }, - "encryptionConfiguration": { - "type": "AWS_OWNED_KEY" - }, - "loggingConfiguration": { - "includeExecutionData": false, - "level": "OFF" - }, - "name": "", - "roleArn": "", - "stateMachineArn": "", - "status": "ACTIVE", - "tracingConfiguration": { - "enabled": false - }, - "type": "STANDARD", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "describe_state_machine_output_on_update": { - "creationDate": "datetime", - "definition": { - "Comment": "step: on update", - "StartAt": "S0", - "States": { - "S0": { - "Type": "Succeed" - } - } - }, - "encryptionConfiguration": { - "type": "AWS_OWNED_KEY" - }, - "loggingConfiguration": { - "includeExecutionData": false, - "level": "OFF" - }, - "name": "", - "revisionId": "", - "roleArn": "", - "stateMachineArn": "", - "status": "ACTIVE", - "tracingConfiguration": { - "enabled": false - }, - "type": "STANDARD", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": { - "recorded-date": "24-03-2025, 21:58:55", - "recorded-content": { - "describe_state_machine_result": { - "creationDate": "datetime", - "definition": { - "StartAt": "S0", - "States": { - "S0": { - "Type": "Pass", - "End": true - } - } - }, - "encryptionConfiguration": { - "type": "AWS_OWNED_KEY" - }, - "loggingConfiguration": { - "destinations": [ - { - "cloudWatchLogsLogGroup": { - "logGroupArn": "" - } - } - ], - "includeExecutionData": true, - "level": "ALL" - }, - "name": "", - "roleArn": "", - "stateMachineArn": "", - "status": "ACTIVE", - "tracingConfiguration": { - "enabled": false - }, - "type": "STANDARD", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.validation.json deleted file mode 100644 index 267fe6634138d..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.validation.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_cfn_statemachine_default_s3_location": { - "last_validated_date": "2024-12-17T16:06:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/resources/test_stepfunctions.py::test_statemachine_create_with_logging_configuration": { - "last_validated_date": "2025-03-24T21:58:55+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py deleted file mode 100644 index 247075382cec5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py +++ /dev/null @@ -1,1288 +0,0 @@ -import base64 -import json -import os -import re -from copy import deepcopy - -import botocore.exceptions -import pytest -import yaml - -from localstack.aws.api.lambda_ import Runtime -from localstack.services.cloudformation.engine.yaml_parser import parse_yaml -from localstack.services.cloudformation.v2.utils import is_v2_engine -from localstack.testing.aws.cloudformation_utils import load_template_file, load_template_raw -from localstack.testing.aws.util import is_aws_cloud -from localstack.testing.pytest import markers -from localstack.testing.pytest.fixtures import StackDeployError -from localstack.utils.common import short_uid -from localstack.utils.files import load_file -from localstack.utils.sync import wait_until - -pytestmark = pytest.mark.skipif( - condition=not is_v2_engine() and not is_aws_cloud(), - reason="Only targeting the new engine", -) - - -def create_macro( - macro_name, function_path, deploy_cfn_template, create_lambda_function, lambda_client -): - macro_function_path = function_path - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=lambda_client, - timeout=1, - ) - - return deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - -class TestTypes: - @markers.aws.validated - def test_implicit_type_conversion(self, deploy_cfn_template, snapshot, aws_client): - snapshot.add_transformer(snapshot.transform.sqs_api()) - stack = deploy_cfn_template( - max_wait=180, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates/engine/implicit_type_conversion.yml", - ), - ) - queue = aws_client.sqs.get_queue_attributes( - QueueUrl=stack.outputs["QueueUrl"], AttributeNames=["All"] - ) - snapshot.match("queue", queue) - - -class TestIntrinsicFunctions: - @pytest.mark.parametrize( - ("intrinsic_fn", "parameter_1", "parameter_2", "expected_bucket_created"), - [ - ("Fn::And", "0", "0", False), - ("Fn::And", "0", "1", False), - ("Fn::And", "1", "0", False), - ("Fn::And", "1", "1", True), - ("Fn::Or", "0", "0", False), - ("Fn::Or", "0", "1", True), - ("Fn::Or", "1", "0", True), - ("Fn::Or", "1", "1", True), - ], - ) - @markers.aws.validated - def test_and_or_functions( - self, - intrinsic_fn, - parameter_1, - parameter_2, - expected_bucket_created, - deploy_cfn_template, - aws_client, - ): - bucket_name = f"ls-bucket-{short_uid()}" - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_intrinsic_functions.yaml" - ), - parameters={ - "Param1": parameter_1, - "Param2": parameter_2, - "BucketName": bucket_name, - }, - template_mapping={ - "intrinsic_fn": intrinsic_fn, - }, - ) - - buckets = aws_client.s3.list_buckets() - bucket_names = [b["Name"] for b in buckets["Buckets"]] - assert (bucket_name in bucket_names) == expected_bucket_created - - @markers.aws.validated - def test_base64_sub_and_getatt_functions(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_getatt_sub_base64.yml" - ) - original_string = f"string-{short_uid()}" - deployed = deploy_cfn_template( - template_path=template_path, parameters={"OriginalString": original_string} - ) - - converted_string = base64.b64encode(bytes(original_string, "utf-8")).decode("utf-8") - assert converted_string == deployed.outputs["Encoded"] - - @pytest.mark.skip(reason="CFNV2:LanguageExtensions") - @markers.aws.validated - def test_split_length_and_join_functions(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_select_split_join.yml" - ) - - first_value = f"string-{short_uid()}" - second_value = f"string-{short_uid()}" - deployed = deploy_cfn_template( - template_path=template_path, - parameters={ - "MultipleValues": f"{first_value};{second_value}", - "Value1": first_value, - "Value2": second_value, - }, - ) - - assert first_value == deployed.outputs["SplitResult"] - assert f"{first_value}_{second_value}" == deployed.outputs["JoinResult"] - - # TODO support join+split and length operations - # assert f"{first_value}_{second_value}" == deployed.outputs["SplitJoin"] - # assert 2 == deployed.outputs["LengthResult"] - - @markers.aws.validated - @pytest.mark.skip(reason="functions not currently supported") - def test_to_json_functions(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/function_to_json_string.yml" - ) - - first_value = f"string-{short_uid()}" - second_value = f"string-{short_uid()}" - deployed = deploy_cfn_template( - template_path=template_path, - parameters={ - "Value1": first_value, - "Value2": second_value, - }, - ) - - json_result = json.loads(deployed.outputs["Result"]) - - assert json_result["key1"] == first_value - assert json_result["key2"] == second_value - assert "value1" == deployed.outputs["Result2"] - - @markers.aws.validated - def test_find_map_function(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/function_find_in_map.yml" - ) - - deployed = deploy_cfn_template( - template_path=template_path, - ) - - assert deployed.outputs["Result"] == "us-east-1" - - @markers.aws.validated - @pytest.mark.skip(reason="function not currently supported") - def test_cidr_function(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_cidr.yml" - ) - - # TODO parametrize parameters and result - deployed = deploy_cfn_template( - template_path=template_path, - parameters={"IpBlock": "10.0.0.0/16", "Count": "1", "CidrBits": "8", "Select": "0"}, - ) - - assert deployed.outputs["Address"] == "10.0.0.0/24" - - @pytest.mark.parametrize( - "region", - [ - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - "ap-southeast-2", - "ap-northeast-1", - "eu-central-1", - "eu-west-1", - ], - ) - @markers.aws.validated - def test_get_azs_function(self, deploy_cfn_template, region, aws_client_factory): - """ - TODO parametrize this test. - For that we need to be able to parametrize the client region. The docs show the we should be - able to put any region in the parameters but it doesn't work. It only accepts the same region from the client config - if you put anything else it just returns an empty list. - """ - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/functions_get_azs.yml" - ) - - aws_client = aws_client_factory(region_name=region) - deployed = deploy_cfn_template( - template_path=template_path, - custom_aws_client=aws_client, - parameters={"DeployRegion": region}, - ) - - azs = deployed.outputs["Zones"].split(";") - assert len(azs) > 0 - assert all(re.match(f"{region}[a-f]", az) for az in azs) - - @markers.aws.validated - def test_sub_not_ready(self, deploy_cfn_template): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/sub_dependencies.yaml" - ) - deploy_cfn_template( - template_path=template_path, - max_wait=120, - ) - - @markers.aws.validated - def test_cfn_template_with_short_form_fn_sub(self, deploy_cfn_template): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/engine/cfn_short_sub.yml" - ), - ) - - result = stack.outputs["Result"] - assert result == "test" - - @markers.aws.validated - def test_sub_number_type(self, deploy_cfn_template): - alarm_name_prefix = "alarm-test-latency-preemptive" - threshold = "1000.0" - period = "60" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/sub_number_type.yml" - ), - parameters={ - "ResourceNamePrefix": alarm_name_prefix, - "RestLatencyPreemptiveAlarmThreshold": threshold, - "RestLatencyPreemptiveAlarmPeriod": period, - }, - ) - - assert stack.outputs["AlarmName"] == f"{alarm_name_prefix}-{period}" - assert stack.outputs["Threshold"] == threshold - assert stack.outputs["Period"] == period - - @markers.aws.validated - def test_join_no_value_construct(self, deploy_cfn_template, snapshot, aws_client): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/engine/join_no_value.yml" - ) - ) - - snapshot.match("join-output", stack.outputs) - - -@pytest.mark.skip(reason="CFNV2:Imports unsupported") -class TestImports: - @markers.aws.validated - def test_stack_imports(self, deploy_cfn_template, aws_client): - queue_name1 = f"q-{short_uid()}" - queue_name2 = f"q-{short_uid()}" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/sqs_export.yml" - ), - parameters={"QueueName": queue_name1}, - ) - stack2 = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/sqs_import.yml" - ), - parameters={"QueueName": queue_name2}, - ) - queue_url1 = aws_client.sqs.get_queue_url(QueueName=queue_name1)["QueueUrl"] - queue_url2 = aws_client.sqs.get_queue_url(QueueName=queue_name2)["QueueUrl"] - - queue_arn1 = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url1, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - queue_arn2 = aws_client.sqs.get_queue_attributes( - QueueUrl=queue_url2, AttributeNames=["QueueArn"] - )["Attributes"]["QueueArn"] - - assert stack2.outputs["MessageQueueArn1"] == queue_arn1 - assert stack2.outputs["MessageQueueArn2"] == queue_arn2 - - -@pytest.mark.skip(reason="CFNV2:Resolve") -class TestSsmParameters: - @markers.aws.validated - def test_create_stack_with_ssm_parameters( - self, create_parameter, deploy_cfn_template, snapshot, aws_client - ): - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.add_transformer(snapshot.transform.key_value("ParameterValue")) - snapshot.add_transformer(snapshot.transform.key_value("ResolvedValue")) - - parameter_name = f"ls-param-{short_uid()}" - parameter_value = f"ls-param-value-{short_uid()}" - create_parameter(Name=parameter_name, Value=parameter_value, Type="String") - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/dynamicparameter_ssm_string.yaml" - ), - template_mapping={"parameter_name": parameter_name}, - ) - - stack_description = aws_client.cloudformation.describe_stacks(StackName=stack.stack_name)[ - "Stacks" - ][0] - snapshot.match("stack-details", stack_description) - - topics = aws_client.sns.list_topics() - topic_arns = [t["TopicArn"] for t in topics["Topics"]] - - matching = [arn for arn in topic_arns if parameter_value in arn] - assert len(matching) == 1 - - tags = aws_client.sns.list_tags_for_resource(ResourceArn=matching[0]) - snapshot.match("topic-tags", tags) - - @markers.aws.validated - def test_resolve_ssm(self, create_parameter, deploy_cfn_template): - parameter_key = f"param-key-{short_uid()}" - parameter_value = f"param-value-{short_uid()}" - create_parameter(Name=parameter_key, Value=parameter_value, Type="String") - - result = deploy_cfn_template( - parameters={"DynamicParameter": parameter_key}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/resolve_ssm.yaml" - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value - - @markers.aws.validated - def test_resolve_ssm_with_version(self, create_parameter, deploy_cfn_template, aws_client): - parameter_key = f"param-key-{short_uid()}" - parameter_value_v0 = f"param-value-{short_uid()}" - parameter_value_v1 = f"param-value-{short_uid()}" - parameter_value_v2 = f"param-value-{short_uid()}" - - create_parameter(Name=parameter_key, Type="String", Value=parameter_value_v0) - - v1 = aws_client.ssm.put_parameter( - Name=parameter_key, Overwrite=True, Type="String", Value=parameter_value_v1 - ) - aws_client.ssm.put_parameter( - Name=parameter_key, Overwrite=True, Type="String", Value=parameter_value_v2 - ) - - result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}:{v1['Version']}"}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/resolve_ssm.yaml" - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value_v1 - - @markers.aws.needs_fixing - def test_resolve_ssm_secure(self, create_parameter, deploy_cfn_template): - parameter_key = f"param-key-{short_uid()}" - parameter_value = f"param-value-{short_uid()}" - - create_parameter(Name=parameter_key, Value=parameter_value, Type="SecureString") - - result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}"}, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/resolve_ssm_secure.yaml" - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value - - @markers.aws.validated - def test_ssm_nested_with_nested_stack(self, s3_create_bucket, deploy_cfn_template, aws_client): - """ - When resolving the references in the cloudformation template for 'Fn::GetAtt' we need to consider the attribute subname. - Eg: In "Fn::GetAtt": "ChildParam.Outputs.Value", where attribute reference is ChildParam.Outputs.Value the: - resource logical id is ChildParam and attribute name is Outputs we need to fetch the Value attribute from the resource properties - of the model instance. - """ - - bucket_name = s3_create_bucket() - domain = "amazonaws.com" if is_aws_cloud() else "localhost.localstack.cloud:4566" - - aws_client.s3.upload_file( - os.path.join(os.path.dirname(__file__), "../../../../templates/nested_child_ssm.yaml"), - Bucket=bucket_name, - Key="nested_child_ssm.yaml", - ) - - key_value = "child-2-param-name" - - deploy_cfn_template( - max_wait=120 if is_aws_cloud() else 20, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/nested_parent_ssm.yaml" - ), - parameters={ - "ChildStackURL": f"https://{bucket_name}.s3.{domain}/nested_child_ssm.yaml", - "KeyValue": key_value, - }, - ) - - ssm_parameter = aws_client.ssm.get_parameter(Name="test-param")["Parameter"]["Value"] - - assert ssm_parameter == key_value - - @markers.aws.validated - def test_create_change_set_with_ssm_parameter_list( - self, deploy_cfn_template, aws_client, region_name, account_id, snapshot - ): - snapshot.add_transformer(snapshot.transform.key_value(key="role-name")) - - parameter_logical_id = "parameter123" - parameter_name = f"ls-param-{short_uid()}" - role_name = f"ls-role-{short_uid()}" - parameter_value = ",".join( - [ - f"arn:aws:ssm:{region_name}:{account_id}:parameter/some/params", - f"arn:aws:ssm:{region_name}:{account_id}:parameter/some/other/params", - ] - ) - snapshot.match("role-name", role_name) - - aws_client.ssm.put_parameter(Name=parameter_name, Value=parameter_value, Type="StringList") - - deploy_cfn_template( - max_wait=120 if is_aws_cloud() else 20, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/dynamicparameter_ssm_list.yaml" - ), - template_mapping={"role_name": role_name}, - parameters={parameter_logical_id: parameter_name}, - ) - role_policy = aws_client.iam.get_role_policy(RoleName=role_name, PolicyName="policy-123") - snapshot.match("iam_role_policy", role_policy) - - -class TestSecretsManagerParameters: - @pytest.mark.skip(reason="CFNV2:Resolve") - @pytest.mark.parametrize( - "template_name", - [ - "resolve_secretsmanager_full.yaml", - "resolve_secretsmanager_partial.yaml", - "resolve_secretsmanager.yaml", - ], - ) - @markers.aws.validated - def test_resolve_secretsmanager(self, create_secret, deploy_cfn_template, template_name): - parameter_key = f"param-key-{short_uid()}" - parameter_value = f"param-value-{short_uid()}" - - create_secret(Name=parameter_key, SecretString=parameter_value) - - result = deploy_cfn_template( - parameters={"DynamicParameter": f"{parameter_key}"}, - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates", - template_name, - ), - ) - - topic_name = result.outputs["TopicName"] - assert topic_name == parameter_value - - -class TestPreviousValues: - @pytest.mark.skip(reason="outputs don't behave well in combination with conditions") - @markers.aws.validated - def test_parameter_usepreviousvalue_behavior( - self, deploy_cfn_template, is_stack_updated, aws_client - ): - template_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_reuse_param.yaml" - ) - - # 1. create with overridden default value. Due to the condition this should neither create the optional topic, - # nor the corresponding output - stack = deploy_cfn_template(template_path=template_path, parameters={"DeployParam": "no"}) - - stack_describe_response = aws_client.cloudformation.describe_stacks( - StackName=stack.stack_name - )["Stacks"][0] - assert len(stack_describe_response["Outputs"]) == 1 - - # 2. update using UsePreviousValue. DeployParam should still be "no", still overriding the default and the only - # change should be the changed tag on the required topic - aws_client.cloudformation.update_stack( - StackName=stack.stack_namestack_name, - TemplateBody=load_template_raw(template_path), - Parameters=[ - {"ParameterKey": "CustomTag", "ParameterValue": "trigger-change"}, - {"ParameterKey": "DeployParam", "UsePreviousValue": True}, - ], - ) - wait_until(is_stack_updated(stack.stack_id)) - stack_describe_response = aws_client.cloudformation.describe_stacks( - StackName=stack.stack_name - )["Stacks"][0] - assert len(stack_describe_response["Outputs"]) == 1 - - # 3. update with setting the deployparam to "yes" not. The condition will evaluate to true and thus create the - # topic + output note: for an even trickier challenge for the cloudformation engine, remove the second parameter - # key. Behavior should stay the same. - aws_client.cloudformation.update_stack( - StackName=stack.stack_name, - TemplateBody=load_template_raw(template_path), - Parameters=[ - {"ParameterKey": "CustomTag", "ParameterValue": "trigger-change-2"}, - {"ParameterKey": "DeployParam", "ParameterValue": "yes"}, - ], - ) - assert is_stack_updated(stack.stack_id) - stack_describe_response = aws_client.cloudformation.describe_stacks( - StackName=stack.stack_id - )["Stacks"][0] - assert len(stack_describe_response["Outputs"]) == 2 - - -@pytest.mark.skip(reason="CFNV2:Imports unsupported") -class TestImportValues: - @markers.aws.validated - def test_cfn_with_exports(self, deploy_cfn_template, aws_client, snapshot): - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/engine/cfn_exports.yml" - ) - ) - - exports = aws_client.cloudformation.list_exports()["Exports"] - filtered = [exp for exp in exports if exp["ExportingStackId"] == stack.stack_id] - filtered.sort(key=lambda x: x["Name"]) - - snapshot.match("exports", filtered) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - snapshot.add_transformer(snapshot.transform.regex(stack.stack_name, "")) - - @markers.aws.validated - def test_import_values_across_stacks(self, deploy_cfn_template, aws_client): - export_name = f"b-{short_uid()}" - - # create stack #1 - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_function_export.yml" - ), - parameters={"BucketExportName": export_name}, - ) - bucket_name1 = result.outputs.get("BucketName1") - assert bucket_name1 - - # create stack #2 - result = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/cfn_function_import.yml" - ), - parameters={"BucketExportName": export_name}, - ) - bucket_name2 = result.outputs.get("BucketName2") - assert bucket_name2 - - # assert that correct bucket tags have been created - tagging = aws_client.s3.get_bucket_tagging(Bucket=bucket_name2) - test_tag = [tag for tag in tagging["TagSet"] if tag["Key"] == "test"] - assert test_tag - assert test_tag[0]["Value"] == bucket_name1 - - # TODO support this method - # assert cfn_client.list_imports(ExportName=export_name)["Imports"] - - -class TestMacros: - @markers.aws.validated - def test_macro_deployment( - self, deploy_cfn_template, create_lambda_function, snapshot, aws_client - ): - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/format_template.py" - ) - macro_name = "SubstitutionMacro" - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - ) - - stack_with_macro = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - description = aws_client.cloudformation.describe_stack_resources( - StackName=stack_with_macro.stack_name - ) - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("stack_outputs", stack_with_macro.outputs) - snapshot.match("stack_resource_descriptions", description) - - @pytest.mark.skip("CFNV2:Macros") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..TemplateBody.Resources.Parameter.LogicalResourceId", - "$..TemplateBody.Conditions", - "$..TemplateBody.Mappings", - "$..TemplateBody.StackId", - "$..TemplateBody.StackName", - "$..TemplateBody.Transform", - ] - ) - def test_global_scope( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - This test validates the behaviour of a template deployment that includes a global transformation - """ - - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/format_template.py" - ) - macro_name = "SubstitutionMacro" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - new_value = f"new-value-{short_uid()}" - stack_name = f"stake-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - TemplateBody=load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_global_parameter.yml", - ) - ), - Parameters=[{"ParameterKey": "Substitution", "ParameterValue": new_value}], - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.add_transformer(snapshot.transform.regex(new_value, "new-value")) - snapshot.match("processed_template", processed_template) - - @pytest.mark.skip( - reason="CFNV2:Fn::Transform as resource property with missing Name and Parameters fields." - ) - @markers.aws.validated - @pytest.mark.parametrize( - "template_to_transform", - ["transformation_snippet_topic.yml", "transformation_snippet_topic.json"], - ) - def test_snipped_scope( - self, - deploy_cfn_template, - create_lambda_function, - snapshot, - template_to_transform, - aws_client, - ): - """ - This test validates the behaviour of a template deployment that includes a snipped transformation also the - responses from the get_template with different template formats. - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/add_standard_attributes.py" - ) - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - macro_name = "ConvertTopicToFifo" - stack_name = f"stake-macro-{short_uid()}" - deploy_cfn_template( - stack_name=stack_name, - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - topic_name = f"topic-{short_uid()}.fifo" - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates", - template_to_transform, - ), - parameters={"TopicName": topic_name}, - ) - original_template = aws_client.cloudformation.get_template( - StackName=stack.stack_name, TemplateStage="Original" - ) - processed_template = aws_client.cloudformation.get_template( - StackName=stack.stack_name, TemplateStage="Processed" - ) - snapshot.add_transformer(snapshot.transform.regex(topic_name, "topic-name")) - - snapshot.match("original_template", original_template) - snapshot.match("processed_template", processed_template) - - @markers.aws.validated - def test_attribute_uses_macro(self, deploy_cfn_template, create_lambda_function, aws_client): - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/return_random_string.py" - ) - - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - ) - - macro_name = "GenerateRandom" - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates", - "transformation_resource_att.yml", - ), - parameters={"Input": "test"}, - ) - - resulting_value = stack.outputs["Parameter"] - assert "test-" in resulting_value - - @markers.aws.validated - @pytest.mark.skip(reason="Fn::Transform does not support array of transformations") - def test_scope_order_and_parameters( - self, deploy_cfn_template, create_lambda_function, snapshot, aws_client - ): - """ - The test validates the order of execution of transformations and also asserts that any type of - transformation can receive inputs. - """ - - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/replace_string.py" - ) - macro_name = "ReplaceString" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_multiple_scope_parameter.yml", - ), - ) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack.stack_name, TemplateStage="Processed" - ) - snapshot.match("processed_template", processed_template) - - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..TemplateBody.Resources.Parameter.LogicalResourceId", - "$..TemplateBody.Conditions", - "$..TemplateBody.Mappings", - "$..TemplateBody.Parameters", - "$..TemplateBody.StackId", - "$..TemplateBody.StackName", - "$..TemplateBody.Transform", - "$..TemplateBody.Resources.Role.LogicalResourceId", - ] - ) - def test_capabilities_requirements( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - The test validates that AWS will return an error about missing CAPABILITY_AUTOEXPAND when adding a - resource during the transformation, and it will ask for CAPABILITY_NAMED_IAM when the new resource is a - IAM role - """ - - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/add_role.py" - ) - macro_name = "AddRole" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack_name = f"stack-{short_uid()}" - args = { - "StackName": stack_name, - "TemplateBody": load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_add_role.yml", - ) - ), - } - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack(**args) - snapshot.match("error", ex.value.response) - - args["Capabilities"] = [ - "CAPABILITY_AUTO_EXPAND", # Required to allow macro to add a role to template - "CAPABILITY_NAMED_IAM", # Required to allow CFn create added role - ] - aws_client.cloudformation.create_stack(**args) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.add_transformer(snapshot.transform.key_value("RoleName", "role-name")) - snapshot.match("processed_template", processed_template) - - @pytest.mark.skip("CFNV2:GetTemplate") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..Event.fragment.Conditions", - "$..Event.fragment.Mappings", - "$..Event.fragment.Outputs", - "$..Event.fragment.Resources.Parameter.LogicalResourceId", - "$..Event.fragment.StackId", - "$..Event.fragment.StackName", - "$..Event.fragment.Transform", - ] - ) - def test_validate_lambda_internals( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - The test validates the content of the event pass into the macro lambda - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/print_internals.py" - ) - - macro_name = "PrintInternals" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack_name = f"stake-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - TemplateBody=load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_print_internals.yml", - ) - ), - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.match( - "event", - processed_template["TemplateBody"]["Resources"]["Parameter"]["Properties"]["Value"], - ) - - @pytest.mark.skip("CFNV2:Validation") - @markers.aws.validated - def test_to_validate_template_limit_for_macro( - self, deploy_cfn_template, create_lambda_function, snapshot, aws_client - ): - """ - The test validates the max size of a template that can be passed into the macro function - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/format_template.py" - ) - macro_name = "FormatTemplate" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - template_dict = parse_yaml( - load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_global_parameter.yml", - ) - ) - ) - for n in range(0, 1000): - template_dict["Resources"][f"Parameter{n}"] = deepcopy( - template_dict["Resources"]["Parameter"] - ) - - template = yaml.dump(template_dict) - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack( - StackName=f"stack-{short_uid()}", TemplateBody=template - ) - - response = ex.value.response - response["Error"]["Message"] = response["Error"]["Message"].replace( - template, "" - ) - snapshot.match("error_response", response) - - @pytest.mark.skip("CFNV2:Validation") - @markers.aws.validated - def test_error_pass_macro_as_reference(self, snapshot, aws_client): - """ - This test shows that the CFn will reject any transformation name that has been specified as reference, for - example, a parameter. - """ - - with pytest.raises(botocore.exceptions.ClientError) as ex: - aws_client.cloudformation.create_stack( - StackName=f"stack-{short_uid()}", - TemplateBody=load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_macro_as_reference.yml", - ) - ), - Capabilities=["CAPABILITY_AUTO_EXPAND"], - Parameters=[{"ParameterKey": "MacroName", "ParameterValue": "NonExistent"}], - ) - snapshot.match("error", ex.value.response) - - @pytest.mark.skip("CFNV2:GetTemplate") - @markers.aws.validated - def test_functions_and_references_during_transformation( - self, deploy_cfn_template, create_lambda_function, snapshot, cleanups, aws_client - ): - """ - This tests shows the state of intrinsic functions during the execution of the macro - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/print_references.py" - ) - macro_name = "PrintReferences" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - stack_name = f"stake-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, - Capabilities=["CAPABILITY_AUTO_EXPAND"], - TemplateBody=load_template_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_macro_params_as_reference.yml", - ) - ), - Parameters=[{"ParameterKey": "MacroInput", "ParameterValue": "CreateStackInput"}], - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - processed_template = aws_client.cloudformation.get_template( - StackName=stack_name, TemplateStage="Processed" - ) - snapshot.match( - "event", - processed_template["TemplateBody"]["Resources"]["Parameter"]["Properties"]["Value"], - ) - - @pytest.mark.skip(reason="CFNV2:Validation") - @pytest.mark.parametrize( - "macro_function", - [ - "return_unsuccessful_with_message.py", - "return_unsuccessful_without_message.py", - "return_invalid_template.py", - "raise_error.py", - ], - ) - @markers.aws.validated - def test_failed_state( - self, - deploy_cfn_template, - create_lambda_function, - snapshot, - cleanups, - macro_function, - aws_client, - ): - """ - This test shows the error responses for different negative responses from the macro lambda - """ - macro_function_path = os.path.join( - os.path.dirname(__file__), "../../../../templates/macros/", macro_function - ) - - macro_name = "Unsuccessful" - func_name = f"test_lambda_{short_uid()}" - create_lambda_function( - func_name=func_name, - handler_file=macro_function_path, - runtime=Runtime.python3_12, - client=aws_client.lambda_, - timeout=1, - ) - - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/macro_resource.yml" - ), - parameters={"FunctionName": func_name, "MacroName": macro_name}, - ) - - template = load_file( - os.path.join( - os.path.dirname(__file__), - "../../../../templates/transformation_unsuccessful.yml", - ) - ) - - stack_name = f"stack-{short_uid()}" - aws_client.cloudformation.create_stack( - StackName=stack_name, Capabilities=["CAPABILITY_AUTO_EXPAND"], TemplateBody=template - ) - cleanups.append(lambda: aws_client.cloudformation.delete_stack(StackName=stack_name)) - - with pytest.raises(botocore.exceptions.WaiterError): - aws_client.cloudformation.get_waiter("stack_create_complete").wait(StackName=stack_name) - - events = aws_client.cloudformation.describe_stack_events(StackName=stack_name)[ - "StackEvents" - ] - - failed_events_by_policy = [ - event - for event in events - if "ResourceStatusReason" in event and event["ResourceStatus"] == "ROLLBACK_IN_PROGRESS" - ] - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("failed_description", failed_events_by_policy[0]) - - @markers.aws.validated - def test_pyplate_param_type_list(self, deploy_cfn_template, aws_client, snapshot): - deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/pyplate_deploy_template.yml" - ), - ) - - tags = "Env=Prod,Application=MyApp,BU=ModernisationTeam" - param_tags = {pair.split("=")[0]: pair.split("=")[1] for pair in tags.split(",")} - - stack_with_macro = deploy_cfn_template( - template_path=os.path.join( - os.path.dirname(__file__), "../../../../templates/pyplate_example.yml" - ), - parameters={"Tags": tags}, - ) - - bucket_name_output = stack_with_macro.outputs["BucketName"] - assert bucket_name_output - - tagging = aws_client.s3.get_bucket_tagging(Bucket=bucket_name_output) - tags_s3 = [tag for tag in tagging["TagSet"]] - - resp = [] - for tag in tags_s3: - if tag["Key"] in param_tags: - assert tag["Value"] == param_tags[tag["Key"]] - resp.append([tag["Key"], tag["Value"]]) - assert len(tags_s3) >= len(param_tags) - snapshot.match("tags", sorted(resp)) - - -class TestStackEvents: - @pytest.mark.skip(reason="CFNV2:Validation") - @markers.aws.validated - @markers.snapshot.skip_snapshot_verify( - paths=[ - "$..EventId", - "$..PhysicalResourceId", - "$..ResourceProperties", - # TODO: we do not maintain parity here, just that the property exists - "$..ResourceStatusReason", - ] - ) - def test_invalid_stack_deploy(self, deploy_cfn_template, aws_client, snapshot): - logical_resource_id = "MyParameter" - template = { - "Resources": { - logical_resource_id: { - "Type": "AWS::SSM::Parameter", - "Properties": { - # invalid: missing required property _type_ - "Value": "abc123", - }, - }, - }, - } - - with pytest.raises(StackDeployError) as exc_info: - deploy_cfn_template(template=json.dumps(template)) - - stack_events = exc_info.value.events - # filter out only the single create event that failed - failed_events = [ - every - for every in stack_events - if every["ResourceStatus"] == "CREATE_FAILED" - and every["LogicalResourceId"] == logical_resource_id - ] - assert len(failed_events) == 1 - failed_event = failed_events[0] - - snapshot.add_transformer(snapshot.transform.cloudformation_api()) - snapshot.match("failed_event", failed_event) - assert "ResourceStatusReason" in failed_event - - -class TestPseudoParameters: - @markers.aws.validated - def test_stack_id(self, deploy_cfn_template, snapshot): - template = { - "Resources": { - "MyParameter": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Type": "String", - "Value": { - "Ref": "AWS::StackId", - }, - }, - }, - }, - "Outputs": { - "StackId": { - "Value": { - "Fn::GetAtt": [ - "MyParameter", - "Value", - ], - }, - }, - }, - } - - stack = deploy_cfn_template(template=json.dumps(template)) - - snapshot.add_transformer(snapshot.transform.regex(stack.stack_id, "")) - - snapshot.match("StackId", stack.outputs["StackId"]) diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.snapshot.json b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.snapshot.json deleted file mode 100644 index bcc4ddf05b2c7..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.snapshot.json +++ /dev/null @@ -1,687 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestTypes::test_implicit_type_conversion": { - "recorded-date": "29-08-2023, 15:21:22", - "recorded-content": { - "queue": { - "Attributes": { - "ApproximateNumberOfMessages": "0", - "ApproximateNumberOfMessagesDelayed": "0", - "ApproximateNumberOfMessagesNotVisible": "0", - "ContentBasedDeduplication": "false", - "CreatedTimestamp": "timestamp", - "DeduplicationScope": "queue", - "DelaySeconds": "2", - "FifoQueue": "true", - "FifoThroughputLimit": "perQueue", - "LastModifiedTimestamp": "timestamp", - "MaximumMessageSize": "262144", - "MessageRetentionPeriod": "345600", - "QueueArn": "arn::sqs::111111111111:", - "ReceiveMessageWaitTimeSeconds": "0", - "SqsManagedSseEnabled": "true", - "VisibilityTimeout": "30" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_global_scope": { - "recorded-date": "30-01-2023, 20:14:48", - "recorded-content": { - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "ParameterName": { - "Value": { - "Ref": "Parameter" - } - } - }, - "Parameters": { - "Substitution": { - "Default": "SubstitutionDefault", - "Type": "String" - } - }, - "Resources": { - "Parameter": { - "Properties": { - "Type": "String", - "Value": "new-value" - }, - "Type": "AWS::SSM::Parameter" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope": { - "recorded-date": "06-12-2022, 09:44:49", - "recorded-content": { - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "ContentBasedDeduplication": true, - "FifoTopic": true, - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_scope_order_and_parameters": { - "recorded-date": "07-12-2022, 09:08:26", - "recorded-content": { - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Resources": { - "Parameter": { - "Properties": { - "Type": "String", - "Value": "snippet-transform second-snippet-transform global-transform second-global-transform " - }, - "Type": "AWS::SSM::Parameter" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": { - "recorded-date": "08-12-2022, 16:24:58", - "recorded-content": { - "original_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": "Parameters:\n TopicName:\n Type: String\n\nResources:\n Topic:\n Type: AWS::SNS::Topic\n Properties:\n TopicName:\n Ref: TopicName\n Fn::Transform: ConvertTopicToFifo\n\nOutputs:\n TopicName:\n Value:\n Fn::GetAtt:\n - Topic\n - TopicName\n", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "ContentBasedDeduplication": true, - "FifoTopic": true, - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": { - "recorded-date": "08-12-2022, 16:25:43", - "recorded-content": { - "original_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "Fn::Transform": "ConvertTopicToFifo", - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "TopicName": { - "Value": { - "Fn::GetAtt": [ - "Topic", - "TopicName" - ] - } - } - }, - "Parameters": { - "TopicName": { - "Type": "String" - } - }, - "Resources": { - "Topic": { - "Properties": { - "ContentBasedDeduplication": true, - "FifoTopic": true, - "TopicName": { - "Ref": "TopicName" - } - }, - "Type": "AWS::SNS::Topic" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_capabilities_requirements": { - "recorded-date": "30-01-2023, 20:15:46", - "recorded-content": { - "error": { - "Error": { - "Code": "InsufficientCapabilitiesException", - "Message": "Requires capabilities : [CAPABILITY_AUTO_EXPAND]", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - }, - "processed_template": { - "StagesAvailable": [ - "Original", - "Processed" - ], - "TemplateBody": { - "Outputs": { - "ParameterName": { - "Value": { - "Ref": "Parameter" - } - } - }, - "Resources": { - "Parameter": { - "Properties": { - "Type": "String", - "Value": "not-important" - }, - "Type": "AWS::SSM::Parameter" - }, - "Role": { - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "AWS": "*" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [ - { - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/AdministratorAccess" - ] - ] - } - ], - "RoleName": "" - }, - "Type": "AWS::IAM::Role" - } - } - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_validate_lambda_internals": { - "recorded-date": "30-01-2023, 20:16:45", - "recorded-content": { - "event": { - "Event": { - "accountId": "111111111111", - "fragment": { - "Parameters": { - "ExampleParameter": { - "Type": "String", - "Default": "example-value" - } - }, - "Resources": { - "Parameter": { - "Type": "AWS::SSM::Parameter", - "Properties": { - "Value": "", - "Type": "String" - } - } - } - }, - "transformId": "111111111111::PrintInternals", - "requestId": "", - "region": "", - "params": { - "Input": "test-input" - }, - "templateParameterValues": { - "ExampleParameter": "example-value" - } - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": { - "recorded-date": "30-01-2023, 20:17:04", - "recorded-content": { - "error_response": { - "Error": { - "Code": "ValidationError", - "Message": "1 validation error detected: Value '' at 'templateBody' failed to satisfy constraint: Member must have length less than or equal to 51200", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": { - "recorded-date": "30-01-2023, 20:17:05", - "recorded-content": { - "error": { - "Error": { - "Code": "ValidationError", - "Message": "Key Name of transform definition must be a string.", - "Type": "Sender" - }, - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 400 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_macro_param_as_reference": { - "recorded-date": "08-12-2022, 11:50:49", - "recorded-content": {} - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": { - "recorded-date": "30-01-2023, 20:17:55", - "recorded-content": { - "event": { - "Params": { - "Input": "CreateStackInput" - }, - "FunctionValue": { - "Fn::Join": [ - " ", - [ - "Hello", - "World" - ] - ] - }, - "ValueOfRef": { - "Ref": "Substitution" - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": { - "recorded-date": "30-01-2023, 20:18:45", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Transform 111111111111::Unsuccessful failed with: failed because it is a test. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": { - "recorded-date": "30-01-2023, 20:19:35", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Transform 111111111111::Unsuccessful failed without an error message.. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": { - "recorded-date": "30-01-2023, 20:20:30", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Template format error: unsupported structure.. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": { - "recorded-date": "30-01-2023, 20:21:20", - "recorded-content": { - "failed_description": { - "EventId": "", - "LogicalResourceId": "", - "PhysicalResourceId": "arn::cloudformation::111111111111:stack//", - "ResourceStatus": "ROLLBACK_IN_PROGRESS", - "ResourceStatusReason": "Received malformed response from transform 111111111111::Unsuccessful. Rollback requested by user.", - "ResourceType": "AWS::CloudFormation::Stack", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "recorded-date": "15-01-2023, 17:54:23", - "recorded-content": { - "stack-details": { - "Capabilities": [ - "CAPABILITY_AUTO_EXPAND", - "CAPABILITY_IAM", - "CAPABILITY_NAMED_IAM" - ], - "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", - "CreationTime": "datetime", - "DisableRollback": false, - "DriftInformation": { - "StackDriftStatus": "NOT_CHECKED" - }, - "EnableTerminationProtection": false, - "LastUpdatedTime": "datetime", - "NotificationARNs": [], - "Parameters": [ - { - "ParameterKey": "parameter123", - "ParameterValue": "", - "ResolvedValue": "" - } - ], - "RollbackConfiguration": {}, - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "StackStatus": "CREATE_COMPLETE", - "Tags": [] - }, - "topic-tags": { - "Tags": [ - { - "Key": "param-value", - "Value": "param " - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_macro_deployment": { - "recorded-date": "30-01-2023, 20:13:58", - "recorded-content": { - "stack_outputs": { - "MacroRef": "SubstitutionMacro" - }, - "stack_resource_descriptions": { - "StackResources": [ - { - "DriftInformation": { - "StackResourceDriftStatus": "NOT_CHECKED" - }, - "LogicalResourceId": "Macro", - "PhysicalResourceId": "SubstitutionMacro", - "ResourceStatus": "CREATE_COMPLETE", - "ResourceType": "AWS::CloudFormation::Macro", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - ], - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": { - "recorded-date": "12-06-2023, 17:08:47", - "recorded-content": { - "failed_event": { - "EventId": "MyParameter-CREATE_FAILED-date", - "LogicalResourceId": "MyParameter", - "PhysicalResourceId": "", - "ResourceProperties": { - "Value": "abc123" - }, - "ResourceStatus": "CREATE_FAILED", - "ResourceStatusReason": "Property validation failure: [The property {/Type} is required]", - "ResourceType": "AWS::SSM::Parameter", - "StackId": "arn::cloudformation::111111111111:stack//", - "StackName": "", - "Timestamp": "timestamp" - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_pyplate_param_type_list": { - "recorded-date": "17-05-2024, 06:19:03", - "recorded-content": { - "tags": [ - [ - "Application", - "MyApp" - ], - [ - "BU", - "ModernisationTeam" - ], - [ - "Env", - "Prod" - ] - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImportValues::test_cfn_with_exports": { - "recorded-date": "21-06-2024, 18:37:15", - "recorded-content": { - "exports": [ - { - "ExportingStackId": "", - "Name": "-TestExport-0", - "Value": "test" - }, - { - "ExportingStackId": "", - "Name": "-TestExport-1", - "Value": "test" - }, - { - "ExportingStackId": "", - "Name": "-TestExport-2", - "Value": "test" - } - ] - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestPseudoParameters::test_stack_id": { - "recorded-date": "18-07-2024, 08:56:47", - "recorded-content": { - "StackId": "" - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": { - "recorded-date": "08-08-2024, 21:21:23", - "recorded-content": { - "role-name": "", - "iam_role_policy": { - "PolicyDocument": { - "Statement": [ - { - "Action": "*", - "Effect": "Allow", - "Resource": [ - "arn::ssm::111111111111:parameter/some/params", - "arn::ssm::111111111111:parameter/some/other/params" - ] - } - ], - "Version": "2012-10-17" - }, - "PolicyName": "policy-123", - "RoleName": "", - "ResponseMetadata": { - "HTTPHeaders": {}, - "HTTPStatusCode": 200 - } - } - } - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": { - "recorded-date": "22-01-2025, 14:01:46", - "recorded-content": { - "join-output": { - "JoinConditionalNoValue": "", - "JoinOnlyNoValue": "", - "JoinWithNoValue": "Sample" - } - } - } -} diff --git a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.validation.json b/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.validation.json deleted file mode 100644 index 408d1213a84b5..0000000000000 --- a/tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.validation.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImportValues::test_cfn_with_exports": { - "last_validated_date": "2024-06-21T18:37:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestImports::test_stack_imports": { - "last_validated_date": "2024-07-04T14:19:31+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_cfn_template_with_short_form_fn_sub": { - "last_validated_date": "2024-06-20T20:41:15+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function": { - "last_validated_date": "2024-04-03T07:12:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-northeast-1]": { - "last_validated_date": "2024-05-09T08:34:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[ap-southeast-2]": { - "last_validated_date": "2024-05-09T08:34:02+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-central-1]": { - "last_validated_date": "2024-05-09T08:34:39+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[eu-west-1]": { - "last_validated_date": "2024-05-09T08:34:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-1]": { - "last_validated_date": "2024-05-09T08:32:56+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-east-2]": { - "last_validated_date": "2024-05-09T08:33:12+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-1]": { - "last_validated_date": "2024-05-09T08:33:29+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_get_azs_function[us-west-2]": { - "last_validated_date": "2024-05-09T08:33:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_join_no_value_construct": { - "last_validated_date": "2025-01-22T14:01:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestIntrinsicFunctions::test_sub_number_type": { - "last_validated_date": "2024-08-09T06:55:16+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_capabilities_requirements": { - "last_validated_date": "2023-01-30T19:15:46+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_error_pass_macro_as_reference": { - "last_validated_date": "2023-01-30T19:17:05+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[raise_error.py]": { - "last_validated_date": "2023-01-30T19:21:20+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_invalid_template.py]": { - "last_validated_date": "2023-01-30T19:20:30+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_with_message.py]": { - "last_validated_date": "2023-01-30T19:18:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_failed_state[return_unsuccessful_without_message.py]": { - "last_validated_date": "2023-01-30T19:19:35+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_functions_and_references_during_transformation": { - "last_validated_date": "2023-01-30T19:17:55+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_global_scope": { - "last_validated_date": "2023-01-30T19:14:48+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_macro_deployment": { - "last_validated_date": "2023-01-30T19:13:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_pyplate_param_type_list": { - "last_validated_date": "2024-05-17T06:19:03+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_scope_order_and_parameters": { - "last_validated_date": "2022-12-07T08:08:26+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.json]": { - "last_validated_date": "2022-12-08T15:25:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_snipped_scope[transformation_snippet_topic.yml]": { - "last_validated_date": "2022-12-08T15:24:58+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_to_validate_template_limit_for_macro": { - "last_validated_date": "2023-01-30T19:17:04+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestMacros::test_validate_lambda_internals": { - "last_validated_date": "2023-01-30T19:16:45+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestPseudoParameters::test_stack_id": { - "last_validated_date": "2024-07-18T08:56:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_change_set_with_ssm_parameter_list": { - "last_validated_date": "2024-08-08T21:21:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_create_stack_with_ssm_parameters": { - "last_validated_date": "2023-01-15T16:54:23+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestSsmParameters::test_ssm_nested_with_nested_stack": { - "last_validated_date": "2024-07-16T16:38:43+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestStackEvents::test_invalid_stack_deploy": { - "last_validated_date": "2023-06-12T15:08:47+00:00" - }, - "tests/aws/services/cloudformation/v2/ported_from_v1/test_template_engine.py::TestTypes::test_implicit_type_conversion": { - "last_validated_date": "2023-08-29T13:21:22+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_conditions.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_conditions.validation.json deleted file mode 100644 index daba45fdabc59..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_conditions.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_negative_condition_to_existent_resource": { - "last_validated_date": "2025-04-15T15:11:48+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_add_new_positive_condition_to_existent_resource": { - "last_validated_date": "2025-04-15T16:00:39+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_adds_resource": { - "last_validated_date": "2025-04-15T14:31:36+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_conditions.py::TestChangeSetConditions::test_condition_update_removes_resource": { - "last_validated_date": "2025-04-15T13:51:50+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_depends_on.validation.json deleted file mode 100644 index 6d50b4297ea1d..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_depends_on.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_addition": { - "last_validated_date": "2025-05-19T18:10:11+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_multiple_dependencies_deletion": { - "last_validated_date": "2025-05-19T18:13:11+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource": { - "last_validated_date": "2025-05-19T12:55:09+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_depends_on.py::TestChangeSetDependsOn::test_update_depended_resource_list": { - "last_validated_date": "2025-05-19T13:01:34+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.validation.json deleted file mode 100644 index b134dc47b4ce5..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change": { - "last_validated_date": "2025-04-08T11:24:14+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_in_get_attr_chain": { - "last_validated_date": "2025-04-08T14:46:11+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_direct_attribute_value_change_with_dependent_addition": { - "last_validated_date": "2025-04-08T12:20:18+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_immutable_property_update_causes_resource_replacement": { - "last_validated_date": "2025-04-08T12:17:00+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_addition": { - "last_validated_date": "2025-04-08T12:33:53+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_get_attr.py::TestChangeSetFnGetAttr::test_resource_deletion": { - "last_validated_date": "2025-04-08T12:36:40+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_join.validation.json deleted file mode 100644 index b8cd37a40d981..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_join.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_indirect_update_refence_argument": { - "last_validated_date": "2025-05-05T13:31:26+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_refence_argument": { - "last_validated_date": "2025-05-05T13:24:03+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_argument": { - "last_validated_date": "2025-05-05T13:10:54+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_arguments_empty": { - "last_validated_date": "2025-05-05T13:42:26+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter": { - "last_validated_date": "2025-05-05T13:15:57+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_join.py::TestChangeSetFnJoin::test_update_string_literal_delimiter_empty": { - "last_validated_date": "2025-05-05T13:37:54+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_select.validation.json deleted file mode 100644 index 49ee9ee8fcdc4..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_select.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_add_to_static_property": { - "last_validated_date": "2025-05-28T13:14:01+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_get_att_reference": { - "last_validated_date": "2025-05-28T14:44:47+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selected_element_type_ref": { - "last_validated_date": "2025-05-28T13:32:24+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_index_only": { - "last_validated_date": "2025-05-28T13:23:46+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_change_in_selection_list": { - "last_validated_date": "2025-05-28T13:21:34+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_select.py::TestChangeSetFnSelect::test_fn_select_remove_from_static_property": { - "last_validated_date": "2025-05-28T13:17:47+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_split.validation.json deleted file mode 100644 index a85de241f5b9d..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_split.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_add_to_static_property": { - "last_validated_date": "2025-06-02T11:19:05+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_delimiter": { - "last_validated_date": "2025-06-02T12:30:32+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_change_source_string_only": { - "last_validated_date": "2025-06-02T11:22:03+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_remove_from_static_property": { - "last_validated_date": "2025-06-02T11:20:29+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_get_att": { - "last_validated_date": "2025-06-02T11:26:00+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_split.py::TestChangeSetFnSplit::test_fn_split_with_ref_as_string_source": { - "last_validated_date": "2025-06-02T11:23:28+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.validation.json deleted file mode 100644 index cd0626345c30e..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_fn_sub.validation.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter": { - "last_validated_date": "2025-05-20T15:26:12+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_literal": { - "last_validated_date": "2025-05-20T11:54:12+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_parameter_ref": { - "last_validated_date": "2025-05-20T15:08:40+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_addition_string_pseudo": { - "last_validated_date": "2025-05-20T09:54:49+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_parameter_literal": { - "last_validated_date": "2025-05-20T12:05:00+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_delete_string_pseudo": { - "last_validated_date": "2025-05-20T11:29:16+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_literal": { - "last_validated_date": "2025-05-20T12:01:36+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_parameter_type": { - "last_validated_date": "2025-05-20T15:10:15+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_fn_sub.py::TestChangeSetFnSub::test_fn_sub_update_string_pseudo": { - "last_validated_date": "2025-05-20T09:59:44+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_mappings.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_mappings.validation.json deleted file mode 100644 index 32d3348a4a4d6..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_mappings.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_addition_with_resource": { - "last_validated_date": "2025-04-15T13:05:52+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_deletion_with_resource_remap": { - "last_validated_date": "2025-04-15T13:08:27+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_addition_with_resource": { - "last_validated_date": "2025-04-15T13:07:01+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_deletion_with_resource_remap": { - "last_validated_date": "2025-04-15T13:15:54+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_key_update": { - "last_validated_date": "2025-04-15T13:04:43+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_mappings.py::TestChangeSetMappings::test_mapping_leaf_update": { - "last_validated_date": "2025-04-15T13:03:18+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_parameters.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_parameters.validation.json deleted file mode 100644 index 05e1a75cbd323..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_parameters.validation.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value": { - "last_validated_date": "2025-04-17T15:35:43+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_default_value_with_dynamic_overrides": { - "last_validated_date": "2025-04-17T15:46:46+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_added_default_value": { - "last_validated_date": "2025-04-17T15:39:55+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_parameters.py::TestChangeSetParameters::test_update_parameter_with_removed_default_value": { - "last_validated_date": "2025-04-17T15:44:24+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_ref.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_ref.validation.json deleted file mode 100644 index 1667558f83add..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_ref.validation.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change": { - "last_validated_date": "2025-04-08T15:36:44+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_in_ref_chain": { - "last_validated_date": "2025-04-08T15:45:54+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_direct_attribute_value_change_with_dependent_addition": { - "last_validated_date": "2025-04-08T15:51:05+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_immutable_property_update_causes_resource_replacement": { - "last_validated_date": "2025-04-08T16:00:20+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_resource_addition": { - "last_validated_date": "2025-04-08T15:22:37+00:00" - }, - "tests/aws/services/cloudformation/v2/test_change_set_ref.py::TestChangeSetRef::test_supported_pseudo_parameter": { - "last_validated_date": "2025-05-19T10:22:18+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_set_values.validation.json b/tests/aws/services/cloudformation/v2/test_change_set_values.validation.json deleted file mode 100644 index 1e1fbea183682..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_set_values.validation.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_set_values.py::TestChangeSetValues::test_property_empy_list": { - "last_validated_date": "2025-05-23T17:56:06+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_change_sets.validation.json b/tests/aws/services/cloudformation/v2/test_change_sets.validation.json deleted file mode 100644 index f31398e53fe2f..0000000000000 --- a/tests/aws/services/cloudformation/v2/test_change_sets.validation.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_dynamic]": { - "last_validated_date": "2025-06-18T19:14:21+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 25.11, - "teardown": 0.14, - "total": 25.25 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_dynamic_parameter_scenarios[change_parameter_for_condition_create_resource]": { - "last_validated_date": "2025-06-18T19:14:47+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 26.23, - "teardown": 0.14, - "total": 26.37 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_base_mapping_scenarios[update_string_referencing_resource]": { - "last_validated_date": "2025-06-18T19:15:45+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 25.01, - "teardown": 0.15, - "total": 25.16 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_conditions": { - "last_validated_date": "2025-06-18T19:13:55+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 37.82, - "teardown": 0.16, - "total": 37.98 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_direct_update": { - "last_validated_date": "2025-06-18T19:04:55+00:00", - "durations_in_seconds": { - "setup": 0.26, - "call": 116.94, - "teardown": 0.15, - "total": 117.35 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_dynamic_update": { - "last_validated_date": "2025-06-18T19:06:59+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 124.05, - "teardown": 0.16, - "total": 124.21 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_execute_with_ref": { - "last_validated_date": "2025-06-18T19:15:20+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 26.07, - "teardown": 6.64, - "total": 32.71 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_parameter_lookup": { - "last_validated_date": "2025-06-18T19:13:18+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 128.68, - "teardown": 0.14, - "total": 128.82 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_mappings_with_static_fields": { - "last_validated_date": "2025-06-18T19:11:09+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 124.56, - "teardown": 0.14, - "total": 124.7 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::TestCaptureUpdateProcess::test_parameter_changes": { - "last_validated_date": "2025-06-18T19:09:04+00:00", - "durations_in_seconds": { - "setup": 0.0, - "call": 124.46, - "teardown": 0.14, - "total": 124.6 - } - }, - "tests/aws/services/cloudformation/v2/test_change_sets.py::test_single_resource_static_update": { - "last_validated_date": "2025-03-18T16:52:35+00:00" - } -} diff --git a/tests/aws/services/cloudformation/v2/test_dynamic_resolving.py b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.py new file mode 100644 index 0000000000000..923e1e829957b --- /dev/null +++ b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.py @@ -0,0 +1,142 @@ +from tests.aws.services.cloudformation.conftest import skip_if_v1_provider + +from localstack.testing.pytest import markers +from localstack.utils.strings import short_uid + +pytestmark = skip_if_v1_provider(reason="Only valid for the V2 provider") + + +@markers.snapshot.skip_snapshot_verify( + paths=[ + "delete-describe..*", + # + # Before/After Context + "$..Capabilities", + "$..IncludeNestedStacks", + "$..Scope", + "$..Details", + "$..Parameters", + "$..Replacement", + "$..PolicyAction", + ] +) +class TestSSMParameterValues: + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: parity of the events + "$..MyParameter..PhysicalResourceId" + ] + ) + def test_update_parameter_between_deployments( + self, aws_client, snapshot, create_parameter, capture_update_process + ): + param_name = f"param-{short_uid()}" + param_value_1 = f"param-value-1-{short_uid()}" + param_value_2 = f"param-value-2-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(param_name, ""), + snapshot.transform.regex(param_value_1, ""), + snapshot.transform.regex(param_value_2, ""), + snapshot.transform.key_value("PhysicalResourceId"), + ] + ) + + create_parameter(Name=param_name, Value=param_value_1, Type="String") + + template1 = { + "Parameters": { + "MyValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "MyValue"}, + }, + }, + }, + } + + def update_parameter_value(): + aws_client.ssm.put_parameter( + Name=param_name, Value=param_value_2, Type="String", Overwrite=True + ) + + capture_update_process( + snapshot=snapshot, + t1=template1, + t2=template1, + p1={"MyValue": param_name}, + p2={"MyValue": param_name}, + custom_update_step=update_parameter_value, + ) + + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify( + paths=[ + # TODO: parity of the events + "$..MyParameter..PhysicalResourceId" + ] + ) + def test_change_parameter_type( + self, aws_client, snapshot, create_parameter, capture_update_process + ): + param_name = f"param-{short_uid()}" + param_value = f"param-value-{short_uid()}" + + snapshot.add_transformers_list( + [ + snapshot.transform.regex(param_name, ""), + snapshot.transform.regex(param_value, ""), + snapshot.transform.key_value("PhysicalResourceId"), + ] + ) + + create_parameter(Name=param_name, Value=param_value, Type="String") + + template1 = { + "Parameters": { + "MyValue": { + "Type": "String", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "MyValue"}, + }, + }, + }, + } + template2 = { + "Parameters": { + "MyValue": { + "Type": "AWS::SSM::Parameter::Value", + }, + }, + "Resources": { + "MyParameter": { + "Type": "AWS::SSM::Parameter", + "Properties": { + "Type": "String", + "Value": {"Ref": "MyValue"}, + }, + }, + }, + } + + capture_update_process( + snapshot=snapshot, + t1=template1, + t2=template2, + p1={"MyValue": param_name}, + p2={"MyValue": param_name}, + ) diff --git a/tests/aws/services/cloudformation/v2/test_dynamic_resolving.snapshot.json b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.snapshot.json new file mode 100644 index 0000000000000..3d7b70dcba4da --- /dev/null +++ b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.snapshot.json @@ -0,0 +1,845 @@ +{ + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_update_parameter_between_deployments": { + "recorded-date": "06-08-2025, 12:36:33", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + }, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_change_parameter_type": { + "recorded-date": "06-08-2025, 12:36:55", + "recorded-content": { + "create-change-set-1": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-1": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Add", + "Details": [], + "LogicalResourceId": "MyParameter", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-1": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-1-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "CREATE_COMPLETE", + "Tags": [] + }, + "create-change-set-2": { + "Id": "arn::cloudformation::111111111111:changeSet/", + "StackId": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2-prop-values": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "AfterContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "BeforeContext": { + "Properties": { + "Value": "", + "Type": "String" + } + }, + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "AfterValue": "", + "Attribute": "Properties", + "AttributeChangeType": "Modify", + "BeforeValue": "", + "Name": "Value", + "Path": "/Properties/Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-change-set-2": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "ChangeSetName": "", + "Changes": [ + { + "ResourceChange": { + "Action": "Modify", + "Details": [ + { + "ChangeSource": "DirectModification", + "Evaluation": "Static", + "Target": { + "Attribute": "Properties", + "Name": "Value", + "RequiresRecreation": "Never" + } + } + ], + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "Replacement": "False", + "ResourceType": "AWS::SSM::Parameter", + "Scope": [ + "Properties" + ] + }, + "Type": "Resource" + } + ], + "CreationTime": "datetime", + "ExecutionStatus": "AVAILABLE", + "IncludeNestedStacks": false, + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "Status": "CREATE_COMPLETE", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "execute-change-set-2": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "post-create-2-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "ChangeSetId": "arn::cloudformation::111111111111:changeSet/", + "CreationTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "EnableTerminationProtection": false, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "UPDATE_COMPLETE", + "Tags": [] + }, + "delete-describe": { + "Capabilities": [ + "CAPABILITY_IAM", + "CAPABILITY_NAMED_IAM", + "CAPABILITY_AUTO_EXPAND" + ], + "CreationTime": "datetime", + "DeletionTime": "datetime", + "DisableRollback": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + }, + "LastUpdatedTime": "datetime", + "NotificationARNs": [], + "Parameters": [ + { + "ParameterKey": "MyValue", + "ParameterValue": "", + "ResolvedValue": "" + } + ], + "RollbackConfiguration": {}, + "StackId": "", + "StackName": "", + "StackStatus": "DELETE_COMPLETE", + "Tags": [] + }, + "per-resource-events": { + "MyParameter": [ + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "MyParameter", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::SSM::Parameter", + "Timestamp": "timestamp" + } + ], + "Stack": [ + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "REVIEW_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "UPDATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + }, + { + "LogicalResourceId": "", + "PhysicalResourceId": "", + "ResourceStatus": "DELETE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": "timestamp" + } + ] + } + } + } +} diff --git a/tests/aws/services/cloudformation/v2/test_dynamic_resolving.validation.json b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.validation.json new file mode 100644 index 0000000000000..f4c0e42c22b65 --- /dev/null +++ b/tests/aws/services/cloudformation/v2/test_dynamic_resolving.validation.json @@ -0,0 +1,20 @@ +{ + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_change_parameter_type": { + "last_validated_date": "2025-08-06T12:36:55+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 21.56, + "teardown": 0.39, + "total": 21.95 + } + }, + "tests/aws/services/cloudformation/v2/test_dynamic_resolving.py::TestSSMParameterValues::test_update_parameter_between_deployments": { + "last_validated_date": "2025-08-06T12:36:33+00:00", + "durations_in_seconds": { + "setup": 1.05, + "call": 22.47, + "teardown": 0.35, + "total": 23.87 + } + } +} diff --git a/tests/aws/services/cloudwatch/test_cloudwatch.py b/tests/aws/services/cloudwatch/test_cloudwatch.py index 3cb2fbb9b73a5..ff8240c3aaa6a 100644 --- a/tests/aws/services/cloudwatch/test_cloudwatch.py +++ b/tests/aws/services/cloudwatch/test_cloudwatch.py @@ -5,7 +5,7 @@ import os import threading import time -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta, timezone from typing import TYPE_CHECKING from urllib.request import Request, urlopen @@ -40,7 +40,7 @@ class TestCloudwatch: def test_put_metric_data_values_list(self, snapshot, aws_client): metric_name = "test-metric" namespace = f"ns-{short_uid()}" - utc_now = datetime.utcnow().replace(tzinfo=timezone.utc) + utc_now = datetime.utcnow().replace(tzinfo=UTC) snapshot.add_transformer( snapshot.transform.key_value("Timestamp", reference_replacement=False) ) @@ -117,7 +117,7 @@ def test_put_metric_data_gzip(self, aws_client, region_name): @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") def test_put_metric_data_validation(self, aws_client): namespace = f"ns-{short_uid()}" - utc_now = datetime.utcnow().replace(tzinfo=timezone.utc) + utc_now = datetime.utcnow().replace(tzinfo=UTC) # test invalid due to having both Values and Value with pytest.raises(Exception) as ex: @@ -212,13 +212,13 @@ def test_get_metric_data(self, aws_client): namespace2 = f"test/{short_uid()}" aws_client.cloudwatch.put_metric_data( - Namespace=namespace1, MetricData=[dict(MetricName="someMetric", Value=23)] + Namespace=namespace1, MetricData=[{"MetricName": "someMetric", "Value": 23}] ) aws_client.cloudwatch.put_metric_data( - Namespace=namespace1, MetricData=[dict(MetricName="someMetric", Value=18)] + Namespace=namespace1, MetricData=[{"MetricName": "someMetric", "Value": 18}] ) aws_client.cloudwatch.put_metric_data( - Namespace=namespace2, MetricData=[dict(MetricName="ug", Value=23)] + Namespace=namespace2, MetricData=[{"MetricName": "ug", "Value": 23}] ) now = datetime.utcnow().replace(microsecond=0) @@ -303,7 +303,7 @@ def _get_metric_data_sum(): @markers.aws.validated def test_get_metric_data_for_multiple_metrics(self, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" aws_client.cloudwatch.put_metric_data( @@ -389,7 +389,7 @@ def assert_results(): ["Sum", "SampleCount", "Minimum", "Maximum", "Average"], ) def test_get_metric_data_stats(self, aws_client, snapshot, stat): - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" aws_client.cloudwatch.put_metric_data( @@ -445,7 +445,7 @@ def assert_results(): @markers.aws.validated def test_get_metric_data_with_dimensions(self, aws_client, snapshot): - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" aws_client.cloudwatch.put_metric_data( @@ -528,7 +528,7 @@ def test_raw_metric_data(self, aws_client, region_name): """ namespace1 = f"test/{short_uid()}" aws_client.cloudwatch.put_metric_data( - Namespace=namespace1, MetricData=[dict(MetricName="someMetric", Value=23)] + Namespace=namespace1, MetricData=[{"MetricName": "someMetric", "Value": 23}] ) # the new v2 provider doesn't need the headers, will return results for all accounts/regions headers = mock_aws_request_headers( @@ -1299,14 +1299,14 @@ def test_put_metric_alarm( "MetricName": metric_name, "Dimensions": dimension, "Value": 21, - "Timestamp": datetime.utcnow().replace(tzinfo=timezone.utc), + "Timestamp": datetime.utcnow().replace(tzinfo=UTC), "Unit": "Seconds", }, { "MetricName": metric_name, "Dimensions": dimension, "Value": 22, - "Timestamp": datetime.utcnow().replace(tzinfo=timezone.utc), + "Timestamp": datetime.utcnow().replace(tzinfo=UTC), "Unit": "Seconds", }, ] @@ -1914,7 +1914,7 @@ def test_metric_widget(self, aws_client): MetricData=[ { "MetricName": metric_name, - "Timestamp": datetime.utcnow().replace(tzinfo=timezone.utc), + "Timestamp": datetime.utcnow().replace(tzinfo=UTC), "Values": [1.0, 10.0], "Counts": [2, 4], "Unit": "Count", @@ -2006,7 +2006,7 @@ def test_set_alarm_invalid_input(self, aws_client, snapshot, cleanups): @markers.aws.validated @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") def test_get_metric_data_with_zero_and_labels(self, aws_client, snapshot): - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace1 = f"test/{short_uid()}" # put metric data @@ -2050,14 +2050,18 @@ def _match_results(): @markers.snapshot.skip_snapshot_verify(paths=["$..Datapoints..Unit"]) def test_get_metric_statistics(self, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" for i in range(10): aws_client.cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ - dict(MetricName="metric", Value=i, Timestamp=utc_now + timedelta(seconds=1)) + { + "MetricName": "metric", + "Value": i, + "Timestamp": utc_now + timedelta(seconds=1), + } ], ) @@ -2106,7 +2110,7 @@ def test_get_metric_data_pagination(self, aws_client): namespace = f"n-sp-{short_uid()}" metric_name = f"m-{short_uid()}" max_data_points = 10 # default is 100,800 according to AWS docs - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) for i in range(0, max_data_points * 2): aws_client.cloudwatch.put_metric_data( Namespace=namespace, @@ -2191,7 +2195,7 @@ def assert_found_in_utc(): def test_default_ordering(self, aws_client): namespace = f"n-sp-{short_uid()}" metric_name = f"m-{short_uid()}" - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) for i in range(0, 10): aws_client.cloudwatch.put_metric_data( Namespace=namespace, @@ -2280,7 +2284,7 @@ def assert_ordering(): def test_handle_different_units(self, aws_client, snapshot): namespace = f"n-sp-{short_uid()}" metric_name = "m-test" - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) aws_client.cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ @@ -2325,7 +2329,7 @@ def assert_results(): def test_get_metric_data_with_different_units(self, aws_client, snapshot): namespace = f"n-sp-{short_uid()}" metric_name = "m-test" - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) aws_client.cloudwatch.put_metric_data( Namespace=namespace, MetricData=[ @@ -2423,7 +2427,7 @@ def test_get_metric_data_different_units_no_unit_in_query( namespace = f"n-sp-{short_uid()}" metric_name = "m-test" - now = datetime.utcnow().replace(tzinfo=timezone.utc) + now = datetime.utcnow().replace(tzinfo=UTC) for m in metric_data: m["MetricName"] = metric_name @@ -2471,7 +2475,7 @@ def assert_results(): @pytest.mark.skipif(is_old_provider(), reason="not supported by the old provider") def test_label_generation(self, aws_client, snapshot, input_pairs): # Whenever values differ for a statistic type or period, that value is added to the label - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace1 = f"test/{short_uid()}" # put metric data @@ -2636,7 +2640,7 @@ def log_group_exists(): @markers.aws.validated def test_get_metric_with_no_results(self, snapshot, aws_client): - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"n-{short_uid()}" metric = f"m-{short_uid()}" @@ -2805,7 +2809,7 @@ def test_delete_alarm(self, aws_client, snapshot): def test_multiple_dimensions_statistics(self, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) namespace = f"test/{short_uid()}" metric_name = "http.server.requests.count" dimensions = [ @@ -2825,7 +2829,7 @@ def test_multiple_dimensions_statistics(self, aws_client, snapshot): "Unit": "Count", "StorageResolution": 1, "Dimensions": dimensions, - "Timestamp": datetime.now(tz=timezone.utc), + "Timestamp": datetime.now(tz=UTC), } ], ) @@ -2838,7 +2842,7 @@ def test_multiple_dimensions_statistics(self, aws_client, snapshot): "Unit": "Count", "StorageResolution": 1, "Dimensions": dimensions, - "Timestamp": datetime.now(tz=timezone.utc), + "Timestamp": datetime.now(tz=UTC), } ], ) @@ -2894,7 +2898,7 @@ def sort_dimensions(data: dict): @pytest.mark.skipif(is_old_provider(), reason="New test for v2 provider") def test_invalid_amount_of_datapoints(self, aws_client, snapshot): snapshot.add_transformer(snapshot.transform.cloudwatch_api()) - utc_now = datetime.now(tz=timezone.utc) + utc_now = datetime.now(tz=UTC) with pytest.raises(ClientError) as ex: aws_client.cloudwatch.get_metric_statistics( Namespace="namespace", diff --git a/tests/aws/services/dynamodb/test_dynamodb.py b/tests/aws/services/dynamodb/test_dynamodb.py index c81e04e1c9b69..bbf212b035820 100644 --- a/tests/aws/services/dynamodb/test_dynamodb.py +++ b/tests/aws/services/dynamodb/test_dynamodb.py @@ -3,7 +3,6 @@ import time from datetime import datetime from time import sleep -from typing import Dict import botocore.exceptions import pytest @@ -1893,7 +1892,7 @@ def test_query_on_deleted_resource(self, dynamodb_create_table, aws_client): rs = aws_client.dynamodb.query( TableName=table_name, - KeyConditionExpression="{} = :username".format(partition_key), + KeyConditionExpression=f"{partition_key} = :username", ExpressionAttributeValues={":username": {"S": "test"}}, ) assert rs["ResponseMetadata"]["HTTPStatusCode"] == 200 @@ -1903,7 +1902,7 @@ def test_query_on_deleted_resource(self, dynamodb_create_table, aws_client): with pytest.raises(Exception) as ctx: aws_client.dynamodb.query( TableName=table_name, - KeyConditionExpression="{} = :username".format(partition_key), + KeyConditionExpression=f"{partition_key} = :username", ExpressionAttributeValues={":username": {"S": "test"}}, ) assert ctx.match("ResourceNotFoundException") @@ -2130,7 +2129,7 @@ def test_dynamodb_idempotent_writing( ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, ) - def _transact_write(_d: Dict): + def _transact_write(_d: dict): return aws_client.dynamodb.transact_write_items( ClientRequestToken="dedupe_token", TransactItems=[ diff --git a/tests/aws/services/ec2/test_ec2.py b/tests/aws/services/ec2/test_ec2.py index bc809e1edd022..bfaa8669c6fb6 100644 --- a/tests/aws/services/ec2/test_ec2.py +++ b/tests/aws/services/ec2/test_ec2.py @@ -4,7 +4,7 @@ import pytest from botocore.exceptions import ClientError from localstack_snapshot.snapshots.transformer import SortingTransformer -from moto.ec2 import ec2_backends +from moto.ec2.models import ec2_backends from moto.ec2.utils import ( random_security_group_id, random_subnet_id, @@ -13,10 +13,12 @@ from localstack.constants import AWS_REGION_US_EAST_1, TAG_KEY_CUSTOM_ID from localstack.services.ec2.patches import SecurityGroupIdentifier, VpcIdentifier +from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.id_generator import localstack_id_manager from localstack.utils.strings import short_uid from localstack.utils.sync import retry +from localstack.utils.urls import localstack_host LOG = logging.getLogger(__name__) @@ -411,6 +413,53 @@ def test_describe_vpc_endpoints_with_filter(self, aws_client, region_name): # clean up aws_client.ec2.delete_vpc(VpcId=vpc_id) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..Groups", "$..ServiceRegion"]) + def test_vpc_endpoint_dns_names( + self, aws_client, create_vpc, region_name, snapshot, cleanups, ec2_create_vpc_endpoint + ): + snapshot.add_transformers_list( + [ + snapshot.transform.key_value("GroupId"), + snapshot.transform.key_value("VpcEndpointId"), + snapshot.transform.key_value("VpcId"), + snapshot.transform.key_value("HostedZoneId"), + snapshot.transform.key_value("subnet-id"), + snapshot.transform.jsonpath( + "$.available-endpoint.NetworkInterfaceIds[*]", + value_replacement="network-interface-id", + ), + snapshot.transform.key_value("dns-suffix"), + snapshot.transform.key_value("host"), + ] + ) + host = "amazonaws.com" if is_aws_cloud() else localstack_host().host_and_port() + snapshot.match("host", host) + + vpc = create_vpc(cidr_block="10.0.0.0/24") + vpc_id = vpc["Vpc"]["VpcId"] + aws_client.ec2.modify_vpc_attribute(VpcId=vpc_id, EnableDnsSupport={"Value": True}) + aws_client.ec2.modify_vpc_attribute(VpcId=vpc_id, EnableDnsHostnames={"Value": True}) + subnet = aws_client.ec2.create_subnet(VpcId=vpc_id, CidrBlock="10.0.0.0/24") + subnet_id = subnet["Subnet"]["SubnetId"] + snapshot.match("subnet-id", subnet_id) + + service_name = f"com.amazonaws.{region_name}.execute-api" + vpc_endpoint = ec2_create_vpc_endpoint( + VpcId=vpc["Vpc"]["VpcId"], + ServiceName=service_name, + VpcEndpointType="Interface", + PrivateDnsEnabled=True, + SubnetIds=[subnet_id], + ) + + # LS only returns one dns entry + vpc_endpoint["DnsEntries"] = vpc_endpoint["DnsEntries"][:1] + snapshot.match( + "dns-suffix", vpc_endpoint["DnsEntries"][0]["DnsName"].split(".")[0].split("-")[-1] + ) + snapshot.match("available-endpoint", vpc_endpoint) + @markers.aws.validated @pytest.mark.parametrize("id_type", ["id", "name"]) def test_modify_launch_template(self, create_launch_template, id_type, aws_client): diff --git a/tests/aws/services/ec2/test_ec2.snapshot.json b/tests/aws/services/ec2/test_ec2.snapshot.json index 026c53fa57960..54f7fa64be9c4 100644 --- a/tests/aws/services/ec2/test_ec2.snapshot.json +++ b/tests/aws/services/ec2/test_ec2.snapshot.json @@ -494,5 +494,60 @@ } } } + }, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vpc_endpoint_dns_names": { + "recorded-date": "29-07-2025, 23:29:35", + "recorded-content": { + "host": "", + "subnet-id": "", + "dns-suffix": "", + "network-interface-id": "", + "available-endpoint": { + "CreationTimestamp": "", + "DnsEntries": [ + { + "DnsName": "-.execute-api..vpce.", + "HostedZoneId": "" + } + ], + "DnsOptions": { + "DnsRecordIpType": "ipv4" + }, + "Groups": [ + { + "GroupId": "", + "GroupName": "default" + } + ], + "IpAddressType": "ipv4", + "NetworkInterfaceIds": [ + "" + ], + "OwnerId": "111111111111", + "PolicyDocument": { + "Statement": [ + { + "Action": "*", + "Effect": "Allow", + "Principal": "*", + "Resource": "*" + } + ] + }, + "PrivateDnsEnabled": true, + "RequesterManaged": false, + "RouteTableIds": [], + "ServiceName": "com.amazonaws..execute-api", + "ServiceRegion": "", + "State": "available", + "SubnetIds": [ + "" + ], + "Tags": [], + "VpcEndpointId": "", + "VpcEndpointType": "Interface", + "VpcId": "" + } + } } } diff --git a/tests/aws/services/ec2/test_ec2.validation.json b/tests/aws/services/ec2/test_ec2.validation.json index c26b3e4033cc4..42aa02a1f0dc0 100644 --- a/tests/aws/services/ec2/test_ec2.validation.json +++ b/tests/aws/services/ec2/test_ec2.validation.json @@ -17,6 +17,15 @@ "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vcp_peering_difference_regions": { "last_validated_date": "2024-06-07T21:28:25+00:00" }, + "tests/aws/services/ec2/test_ec2.py::TestEc2Integrations::test_vpc_endpoint_dns_names": { + "last_validated_date": "2025-07-29T23:29:36+00:00", + "durations_in_seconds": { + "setup": 0.75, + "call": 59.53, + "teardown": 122.78, + "total": 183.06 + } + }, "tests/aws/services/ec2/test_ec2.py::test_describe_availability_zones_filter_with_zone_ids": { "last_validated_date": "2025-05-28T09:17:24+00:00" }, diff --git a/tests/aws/services/events/conftest.py b/tests/aws/services/events/conftest.py index 77b9d925e033c..3b9d9068af98f 100644 --- a/tests/aws/services/events/conftest.py +++ b/tests/aws/services/events/conftest.py @@ -1,6 +1,5 @@ import json import logging -from typing import Tuple import pytest @@ -30,42 +29,6 @@ def _create_default_or_custom_event_bus(event_bus_type: str = "default"): return _create_default_or_custom_event_bus -@pytest.fixture -def create_role_event_bus_source_to_bus_target(create_iam_role_with_policy): - def _create_role_event_bus_to_bus(): - assume_role_policy_document_bus_source_to_bus_target = { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": {"Service": "events.amazonaws.com"}, - "Action": "sts:AssumeRole", - } - ], - } - - policy_document_bus_source_to_bus_target = { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "", - "Effect": "Allow", - "Action": "events:PutEvents", - "Resource": "arn:aws:events:*:*:event-bus/*", - } - ], - } - - role_arn_bus_source_to_bus_target = create_iam_role_with_policy( - RoleDefinition=assume_role_policy_document_bus_source_to_bus_target, - PolicyDefinition=policy_document_bus_source_to_bus_target, - ) - - return role_arn_bus_source_to_bus_target - - yield _create_role_event_bus_to_bus - - @pytest.fixture def events_create_archive(aws_client, region_name, account_id): archives = [] @@ -192,7 +155,7 @@ def put_events_with_filter_to_sqs( ): def _put_events_with_filter_to_sqs( pattern: dict, - entries_asserts: list[Tuple[list[dict], bool]], + entries_asserts: list[tuple[list[dict], bool]], event_bus_name: str = None, input_path: str = None, input_transformer: dict[dict, str] = None, @@ -203,7 +166,7 @@ def _put_events_with_filter_to_sqs( event_bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=event_bus_name) - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() events_put_rule( Name=rule_name, @@ -350,47 +313,6 @@ def _add_resource_policy_logs_events_access(log_group_arn: str): aws_client.logs.delete_resource_policy(policyName=policy_name) -@pytest.fixture -def get_primary_secondary_client( - aws_client_factory, - secondary_aws_client_factory, - region_name, - secondary_region_name, - account_id, - secondary_account_id, -): - def _get_primary_secondary_clients(cross_scenario: str): - secondary_region = secondary_region_name - secondary_account = secondary_account_id - if cross_scenario not in ["region", "account", "region_account"]: - raise ValueError(f"cross_scenario {cross_scenario} not supported") - - primary_client = aws_client_factory(region_name=region_name) - - if cross_scenario == "region": - secondary_account = account_id - secondary_client = aws_client_factory(region_name=secondary_region_name) - - elif cross_scenario == "account": - secondary_region = region_name - secondary_client = secondary_aws_client_factory(region_name=region_name) - - elif cross_scenario == "region_account": - secondary_client = secondary_aws_client_factory(region_name=secondary_region) - - else: - raise ValueError(f"cross_scenario {cross_scenario} not supported") - - return { - "primary_aws_client": primary_client, - "secondary_aws_client": secondary_client, - "secondary_region_name": secondary_region, - "secondary_account_id": secondary_account, - } - - return _get_primary_secondary_clients - - @pytest.fixture def connection_name(): return f"test-connection-{short_uid()}" diff --git a/tests/aws/services/events/helper_functions.py b/tests/aws/services/events/helper_functions.py index 0c39f6b7b813a..7ddd238f7c1a4 100644 --- a/tests/aws/services/events/helper_functions.py +++ b/tests/aws/services/events/helper_functions.py @@ -1,6 +1,6 @@ import json import os -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from localstack.testing.aws.util import is_aws_cloud from localstack.utils.sync import retry @@ -24,7 +24,7 @@ def events_time_string_to_timestamp(time_string: str) -> datetime: def get_cron_expression(delta_minutes: int) -> tuple[str, datetime]: """Get a exact cron expression for a future time in UTC from now rounded to the next full minute + delta_minutes.""" - now = datetime.now(timezone.utc) + now = datetime.now(UTC) future_time = now + timedelta(minutes=delta_minutes) # Round to the next full minute diff --git a/tests/aws/services/events/test_archive_and_replay.py b/tests/aws/services/events/test_archive_and_replay.py index 18c18804c24f4..8f70514949a80 100644 --- a/tests/aws/services/events/test_archive_and_replay.py +++ b/tests/aws/services/events/test_archive_and_replay.py @@ -1,5 +1,5 @@ import json -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta import pytest from botocore.exceptions import ClientError @@ -377,7 +377,7 @@ def test_start_list_describe_canceled_replay( aws_client, snapshot, ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) # setup event bus @@ -393,7 +393,7 @@ def test_start_list_describe_canceled_replay( rule_arn = response["RuleArn"] # setup sqs target - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() target_id = f"target-{short_uid()}" aws_client.events.put_targets( Rule=rule_name, @@ -503,7 +503,7 @@ def test_start_list_describe_canceled_replay( def test_list_replays_with_prefix( self, events_create_archive, events_create_event_bus, aws_client, snapshot ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) event_bus_name = f"test-bus-{short_uid()}" @@ -558,7 +558,7 @@ def test_list_replays_with_prefix( def test_list_replays_with_event_source_arn( self, events_create_event_bus, events_create_archive, aws_client, snapshot ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) event_bus_name = f"test-bus-{short_uid()}" @@ -596,7 +596,7 @@ def test_list_replays_with_event_source_arn( def test_list_replay_with_limit( self, events_create_event_bus, events_create_archive, aws_client, snapshot ): - event_start_time = datetime.now(timezone.utc) + event_start_time = datetime.now(UTC) event_end_time = event_start_time + timedelta(minutes=1) event_bus_name = f"test-bus-{short_uid()}" @@ -666,8 +666,8 @@ def test_start_replay_error_unknown_event_bus( f"arn:aws:events:{region_name}:{account_id}:event-bus/{not_existing_event_bus_name}" ) - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) replay_name = f"test-replay-{short_uid()}" with pytest.raises(ClientError) as error: @@ -708,8 +708,8 @@ def test_start_replay_error_unknown_archive( self, aws_client, region_name, account_id, snapshot ): not_existing_archive_name = f"doesnotexist-{short_uid()}" - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) with pytest.raises(ClientError) as error: aws_client.events.start_replay( ReplayName="test-replay", @@ -739,8 +739,8 @@ def test_start_replay_error_duplicate_name_same_archive( ] replay_name = f"test-replay-{short_uid()}" - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) aws_client.events.start_replay( ReplayName=replay_name, Description="description of the replay", @@ -786,8 +786,8 @@ def test_start_replay_error_duplicate_different_archive( "ArchiveArn" ] - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) replay_name = f"test-replay-{short_uid()}" aws_client.events.start_replay( @@ -828,7 +828,7 @@ def test_start_replay_error_invalid_end_time( response = events_create_archive() archive_arn = response["ArchiveArn"] - start_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) end_time = start_time.replace(microsecond=0) - timedelta( seconds=negative_time_delta_seconds ) @@ -864,8 +864,8 @@ def tests_concurrency_error_too_many_active_replays( )["ArchiveArn"] replay_name_prefix = short_uid() - start_time = datetime.now(timezone.utc) - timedelta(minutes=1) - end_time = datetime.now(timezone.utc) + start_time = datetime.now(UTC) - timedelta(minutes=1) + end_time = datetime.now(UTC) num_replays = 10 for i in range(num_replays): diff --git a/tests/aws/services/events/test_events.py b/tests/aws/services/events/test_events.py index cb748eb832c1c..67cfbd6d426fa 100644 --- a/tests/aws/services/events/test_events.py +++ b/tests/aws/services/events/test_events.py @@ -293,8 +293,8 @@ def test_put_events_response_entries_order( ): """Test that put_events response contains each EventId only once, even with multiple targets.""" - queue_url_1, queue_arn_1 = sqs_as_events_target() - queue_url_2, queue_arn_2 = sqs_as_events_target() + queue_url_1, queue_arn_1, _ = sqs_as_events_target() + queue_url_2, queue_arn_2, _ = sqs_as_events_target() rule_name = f"test-rule-{short_uid()}" @@ -454,7 +454,7 @@ def test_put_events_with_time_field( ): """Test that EventBridge correctly handles datetime serialization in events.""" rule_name = f"test-rule-{short_uid()}" - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() snapshot.add_transformers_list( [ @@ -886,8 +886,8 @@ def test_put_events_bus_to_bus( ): monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) - bus_name_one = "bus1-{}".format(short_uid()) - bus_name_two = "bus2-{}".format(short_uid()) + bus_name_one = f"bus1-{short_uid()}" + bus_name_two = f"bus2-{short_uid()}" events_create_event_bus(Name=bus_name_one) event_bus_2_arn = events_create_event_bus(Name=bus_name_two)["EventBusArn"] @@ -954,7 +954,7 @@ def test_put_events_bus_to_bus( ) # Create sqs target - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() # Rule and target bus 2 to sqs rule_name_bus_two = f"rule-{short_uid()}" diff --git a/tests/aws/services/events/test_events_inputs.py b/tests/aws/services/events/test_events_inputs.py index 65e225a460c87..68c9925d16985 100644 --- a/tests/aws/services/events/test_events_inputs.py +++ b/tests/aws/services/events/test_events_inputs.py @@ -36,7 +36,7 @@ def test_put_event_input_path_and_input_transformer( sqs_as_events_target, events_create_event_bus, events_put_rule, aws_client, snapshot ): - _, queue_arn = sqs_as_events_target() + _, queue_arn, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -164,8 +164,8 @@ def test_put_events_with_input_path_multiple_targets( snapshot, ): # prepare target queues - queue_url_1, queue_arn_1 = sqs_as_events_target() - queue_url_2, queue_arn_2 = sqs_as_events_target() + queue_url_1, queue_arn_1, _ = sqs_as_events_target() + queue_url_2, queue_arn_2, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -355,7 +355,7 @@ def test_put_events_with_input_transformer_missing_keys( aws_client_factory, snapshot, ): - _, queue_arn = sqs_as_events_target() + _, queue_arn, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) @@ -424,7 +424,7 @@ def test_input_transformer_predefined_variables( # https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html#eb-transform-input-predefined # prepare target queues - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() bus_name = f"test-bus-{short_uid()}" events_create_event_bus(Name=bus_name) diff --git a/tests/aws/services/events/test_events_patterns.py b/tests/aws/services/events/test_events_patterns.py index a8af3d5cc1a8b..8c5bc3a73789c 100644 --- a/tests/aws/services/events/test_events_patterns.py +++ b/tests/aws/services/events/test_events_patterns.py @@ -3,7 +3,6 @@ import os from datetime import datetime from pathlib import Path -from typing import List, Tuple import json5 import pytest @@ -26,18 +25,18 @@ TEST_PAYLOAD_DIR = os.path.join(THIS_FOLDER, "test_payloads") -def load_request_templates(directory_path: str) -> List[Tuple[dict, str]]: +def load_request_templates(directory_path: str) -> list[tuple[dict, str]]: json5_files = list_files_with_suffix(directory_path, ".json5") return [load_request_template(file_path) for file_path in json5_files] -def load_request_template(file_path: str) -> Tuple[dict, str]: - with open(file_path, "r") as df: +def load_request_template(file_path: str) -> tuple[dict, str]: + with open(file_path) as df: template = json5.load(df) return template, Path(file_path).stem -def list_files_with_suffix(directory_path: str, suffix: str) -> List[str]: +def list_files_with_suffix(directory_path: str, suffix: str) -> list[str]: files = [] for root, _, filenames in os.walk(directory_path): for filename in filenames: @@ -117,8 +116,8 @@ def test_event_pattern_with_multi_key(self, aws_client): """ with ( - open(COMPLEX_MULTI_KEY_EVENT, "r") as event_file, - open(COMPLEX_MULTI_KEY_EVENT_PATTERN, "r") as event_pattern_file, + open(COMPLEX_MULTI_KEY_EVENT) as event_file, + open(COMPLEX_MULTI_KEY_EVENT_PATTERN) as event_pattern_file, ): event = event_file.read() event_pattern = event_pattern_file.read() @@ -446,7 +445,7 @@ def test_put_event_with_content_base_rule_in_pattern( snapshot, aws_client, ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() # Create event bus event_bus_name = f"event-bus-{short_uid()}" diff --git a/tests/aws/services/events/test_events_schedule.py b/tests/aws/services/events/test_events_schedule.py index aef36fadb04f2..a091238382526 100644 --- a/tests/aws/services/events/test_events_schedule.py +++ b/tests/aws/services/events/test_events_schedule.py @@ -1,6 +1,6 @@ import json import time -from datetime import timedelta, timezone +from datetime import UTC, timedelta import pytest from botocore.exceptions import ClientError @@ -148,7 +148,7 @@ def tests_schedule_rate_target_sqs( def tests_schedule_rate_custom_input_target_sqs( self, sqs_as_events_target, events_put_rule, aws_client, snapshot ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() bus_name = "default" rule_name = f"test-rule-{short_uid()}" @@ -343,7 +343,7 @@ def test_schedule_cron_target_sqs( aws_client, snapshot, ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() schedule_cron, target_datetime = get_cron_expression( 1 @@ -377,7 +377,7 @@ def test_schedule_cron_target_sqs( # check if message was delivered at the correct time time_message = events_time_string_to_timestamp( json.loads(messages[0]["Body"])["time"] - ).replace(tzinfo=timezone.utc) + ).replace(tzinfo=UTC) # TODO fix JobScheduler to execute on exact time # round datetime to nearest minute @@ -391,7 +391,7 @@ def test_schedule_cron_target_sqs( def tests_scheduled_rule_does_not_trigger_on_put_events( self, sqs_as_events_target, events_put_rule, aws_client ): - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() bus_name = "default" rule_name = f"test-rule-{short_uid()}" diff --git a/tests/aws/services/events/test_events_targets.py b/tests/aws/services/events/test_events_targets.py index a4c641466f4d2..bddbb5612d835 100644 --- a/tests/aws/services/events/test_events_targets.py +++ b/tests/aws/services/events/test_events_targets.py @@ -639,7 +639,7 @@ def test_put_events_with_target_events( EventPattern=json.dumps(TEST_EVENT_PATTERN), ) - queue_url, queue_arn = sqs_as_events_target() + queue_url, queue_arn, _ = sqs_as_events_target() target_id = f"target-{short_uid()}" aws_client.events.put_targets( Rule=rule_name_target_to_sqs, diff --git a/tests/aws/services/firehose/conftest.py b/tests/aws/services/firehose/conftest.py index a0b903ccdbf4b..de0933521f838 100644 --- a/tests/aws/services/firehose/conftest.py +++ b/tests/aws/services/firehose/conftest.py @@ -22,7 +22,7 @@ def _get_data(): keys = [obj.get("Key") for obj in response.get("Contents")] - bucket_data = dict() + bucket_data = {} for key in keys: response = s3.get_object(Bucket=bucket_name, Key=key) data = response["Body"].read().decode("utf-8") diff --git a/tests/aws/services/firehose/helper_functions.py b/tests/aws/services/firehose/helper_functions.py index 6993e264734d4..84ffa79958db4 100644 --- a/tests/aws/services/firehose/helper_functions.py +++ b/tests/aws/services/firehose/helper_functions.py @@ -1,8 +1,5 @@ -from typing import Union - - def get_firehose_iam_documents( - bucket_arns: Union[list[str], str], stream_arns: Union[list[str], str] + bucket_arns: list[str] | str, stream_arns: list[str] | str ) -> tuple[dict, dict]: """ Generate the required IAM role and policy documents for Firehose. diff --git a/tests/aws/services/firehose/test_firehose.py b/tests/aws/services/firehose/test_firehose.py index 33497aa875fb5..c830d5770ba12 100644 --- a/tests/aws/services/firehose/test_firehose.py +++ b/tests/aws/services/firehose/test_firehose.py @@ -518,7 +518,7 @@ def test_kinesis_firehose_kinesis_as_source_multiple_delivery_streams( ) # poll file from s3 buckets - s3_data = dict() + s3_data = {} for bucket_name in [bucket_a_name, bucket_b_name]: s3_data_bucket = read_s3_data(bucket_name, timeout=300) assert len(s3_data_bucket.keys()) == 1 diff --git a/tests/aws/services/kms/test_kms.py b/tests/aws/services/kms/test_kms.py index 87e9aa220206b..97943728ce9b5 100644 --- a/tests/aws/services/kms/test_kms.py +++ b/tests/aws/services/kms/test_kms.py @@ -1702,6 +1702,195 @@ def test_hmac_create_key_invalid_operations(self, kms_create_key, snapshot, regi ) snapshot.match("create-hmac-key-invalid-key-usage", e.value.response) + @markers.aws.validated + @pytest.mark.parametrize( + "key_spec, curve, oaep_hash, signing_algorithm, wrapping_key_spec, wrapping_algorithm", + [ + ( + "ECC_NIST_P256", + ec.SECP256R1(), + hashes.SHA1(), + "ECDSA_SHA_256", + "RSA_2048", + "RSAES_OAEP_SHA_1", + ), + ( + "ECC_NIST_P384", + ec.SECP384R1(), + hashes.SHA1(), + "ECDSA_SHA_384", + "RSA_2048", + "RSAES_OAEP_SHA_1", + ), + ( + "ECC_NIST_P521", + ec.SECP521R1(), + hashes.SHA256(), + "ECDSA_SHA_512", + "RSA_4096", + "RSAES_OAEP_SHA_256", + ), + ( + "ECC_SECG_P256K1", + ec.SECP256K1(), + hashes.SHA1(), + "ECDSA_SHA_256", + "RSA_2048", + "RSAES_OAEP_SHA_1", + ), + ], + ids=["ECC_NIST_P256", "ECC_NIST_P384", "ECC_NIST_P521", "ECC_SECG_P256K1"], + ) + def test_import_key_ecc_keys( + self, + key_spec, + curve, + oaep_hash, + signing_algorithm, + wrapping_key_spec, + wrapping_algorithm, + kms_create_key, + aws_client, + snapshot, + ): + key = kms_create_key( + Origin="EXTERNAL", + KeySpec=key_spec, + KeyUsage="SIGN_VERIFY", + Description="test ecc key import", + ) + key_id = key["KeyId"] + + ecc_key = ec.generate_private_key(curve) + raw_private_key = ecc_key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), + ) + raw_public_key = ecc_key.public_key().public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + import_params = aws_client.kms.get_parameters_for_import( + KeyId=key_id, + WrappingAlgorithm=wrapping_algorithm, + WrappingKeySpec=wrapping_key_spec, + ) + + public_key = load_der_public_key(import_params["PublicKey"]) + encrypted_key = public_key.encrypt( + raw_private_key, + padding.OAEP( + mgf=padding.MGF1(algorithm=oaep_hash), + algorithm=oaep_hash, + label=None, + ), + ) + + describe_before = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-before-import", describe_before) + + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=import_params["ImportToken"], + EncryptedKeyMaterial=encrypted_key, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + + describe_after = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-import", describe_after) + + get_public_key_after_import = aws_client.kms.get_public_key(KeyId=key_id) + assert get_public_key_after_import["PublicKey"] == raw_public_key + + message = b"test sign verify 123 !%$@ 1234567890" + sign_result = aws_client.kms.sign( + KeyId=key_id, + Message=message, + MessageType="RAW", + SigningAlgorithm=signing_algorithm, + ) + snapshot.match("sign-result", sign_result) + + verify_result = aws_client.kms.verify( + KeyId=key_id, + Message=message, + MessageType="RAW", + SigningAlgorithm=signing_algorithm, + Signature=sign_result["Signature"], + ) + snapshot.match("verify-result", verify_result) + assert verify_result["SignatureValid"] + + aws_client.kms.delete_imported_key_material(KeyId=key_id) + describe_deleted = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-deleted-import", describe_deleted) + + @markers.aws.validated + @pytest.mark.parametrize( + "key_spec, key_length, mac_algo", + [ + ("HMAC_224", 28, "HMAC_SHA_224"), + ("HMAC_256", 32, "HMAC_SHA_256"), + ("HMAC_384", 48, "HMAC_SHA_384"), + ("HMAC_512", 64, "HMAC_SHA_512"), + ], + ids=["HMAC_224", "HMAC_256", "HMAC_384", "HMAC_512"], + ) + def test_import_key_hmac_keys( + self, key_spec, key_length, mac_algo, kms_create_key, aws_client, snapshot + ): + key = kms_create_key( + Origin="EXTERNAL", + KeySpec=key_spec, + KeyUsage="GENERATE_VERIFY_MAC", + Description="test import hmac key", + ) + key_id = key["KeyId"] + + plaintext = os.urandom(key_length) + + params = aws_client.kms.get_parameters_for_import( + KeyId=key_id, WrappingAlgorithm="RSAES_OAEP_SHA_256", WrappingKeySpec="RSA_4096" + ) + + public_key = load_der_public_key(params["PublicKey"]) + encrypted_key = public_key.encrypt( + plaintext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None + ), + ) + describe_key_before_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-before-import", describe_key_before_import) + + aws_client.kms.import_key_material( + KeyId=key_id, + ImportToken=params["ImportToken"], + EncryptedKeyMaterial=encrypted_key, + ExpirationModel="KEY_MATERIAL_DOES_NOT_EXPIRE", + ) + describe_key_after_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-import", describe_key_after_import) + + # Generate and verify MAC + message = b"test-mac-message" + generate_mac = aws_client.kms.generate_mac( + KeyId=key_id, Message=message, MacAlgorithm=mac_algo + ) + snapshot.match("generate-mac-response", generate_mac) + + verify_mac = aws_client.kms.verify_mac( + KeyId=key_id, Message=message, Mac=generate_mac["Mac"], MacAlgorithm=mac_algo + ) + snapshot.match("verify-mac-response", verify_mac) + assert verify_mac["MacValid"] + + aws_client.kms.delete_imported_key_material(KeyId=key_id) + describe_key_after_deleted_import = aws_client.kms.describe_key(KeyId=key_id) + snapshot.match("describe-key-after-deleted-import", describe_key_after_deleted_import) + @markers.aws.validated @pytest.mark.parametrize( "key_spec,mac_algo", @@ -2006,6 +2195,22 @@ def test_derive_shared_secret(self, kms_create_key, aws_client, snapshot): ) snapshot.match("response-invalid-public-key", e.value.response) + @markers.aws.validated + @markers.snapshot.skip_snapshot_verify(paths=["$..CurrentKeyMaterialId"]) + def test_describe_with_alias_arn(self, kms_create_key, aws_client, snapshot): + snapshot.add_transformer(snapshot.transform.key_value("CurrentKeyMaterialId")) + + alias_name = f"alias/{short_uid()}" + created_key = kms_create_key(Description="test - description") + snapshot.match("created-key", created_key) + + aws_client.kms.create_alias(TargetKeyId=created_key["KeyId"], AliasName=alias_name) + alias = _get_alias(aws_client.kms, alias_name) + alias_arn = alias["AliasArn"] + + describe_response = aws_client.kms.describe_key(KeyId=alias_arn) + snapshot.match("describe-key", describe_response) + class TestKMSMultiAccounts: @markers.aws.needs_fixing diff --git a/tests/aws/services/kms/test_kms.snapshot.json b/tests/aws/services/kms/test_kms.snapshot.json index b771379892ace..93c67bca92401 100644 --- a/tests/aws/services/kms/test_kms.snapshot.json +++ b/tests/aws/services/kms/test_kms.snapshot.json @@ -2378,5 +2378,822 @@ } } } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_with_alias_arn": { + "recorded-date": "05-08-2025, 17:00:25", + "recorded-content": { + "created-key": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test - description", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "AWS_KMS" + }, + "describe-key": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CurrentKeyMaterialId": "", + "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT", + "Description": "test - description", + "Enabled": true, + "EncryptionAlgorithms": [ + "SYMMETRIC_DEFAULT" + ], + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "SYMMETRIC_DEFAULT", + "KeyState": "Enabled", + "KeyUsage": "ENCRYPT_DECRYPT", + "MultiRegion": false, + "Origin": "AWS_KMS" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P256]": { + "recorded-date": "04-08-2025, 08:10:22", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P256", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P256", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P256", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P256", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P256", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P256", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P384]": { + "recorded-date": "04-08-2025, 08:10:26", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P384", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P384", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_384" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P384", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P384", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_384" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_384", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_384", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P384", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P384", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_384" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_SECG_P256K1]": { + "recorded-date": "04-08-2025, 08:10:33", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_SECG_P256K1", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_SECG_P256K1", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_SECG_P256K1", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_SECG_P256K1", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_SECG_P256K1", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_SECG_P256K1", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_256" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P521]": { + "recorded-date": "04-08-2025, 08:10:29", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P521", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P521", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_512" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P521", + "Description": "test ecc key import", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P521", + "KeyState": "Enabled", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_512" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "sign-result": { + "KeyId": "arn::kms::111111111111:key/", + "Signature": "", + "SigningAlgorithm": "ECDSA_SHA_512", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-result": { + "KeyId": "arn::kms::111111111111:key/", + "SignatureValid": true, + "SigningAlgorithm": "ECDSA_SHA_512", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "ECC_NIST_P521", + "Description": "test ecc key import", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "ECC_NIST_P521", + "KeyState": "PendingImport", + "KeyUsage": "SIGN_VERIFY", + "MultiRegion": false, + "Origin": "EXTERNAL", + "SigningAlgorithms": [ + "ECDSA_SHA_512" + ] + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_224]": { + "recorded-date": "04-08-2025, 08:11:28", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_224", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_224", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_224" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_224", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_224", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_224" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_224", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_224", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_224", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_224", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_224" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_256]": { + "recorded-date": "04-08-2025, 08:11:32", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_256", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_256", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_256" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_256", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_256", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_256" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_256", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_256", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_256", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_256", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_256" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_384]": { + "recorded-date": "04-08-2025, 08:11:35", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_384", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_384", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_384" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_384", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_384", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_384" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_384", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_384", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_384", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_384", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_384" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_512]": { + "recorded-date": "04-08-2025, 08:11:38", + "recorded-content": { + "describe-key-before-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_512", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_512", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_512" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_512", + "Description": "test import hmac key", + "Enabled": true, + "ExpirationModel": "KEY_MATERIAL_DOES_NOT_EXPIRE", + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_512", + "KeyState": "Enabled", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_512" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "generate-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "Mac": "", + "MacAlgorithm": "HMAC_SHA_512", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "verify-mac-response": { + "KeyId": "arn::kms::111111111111:key/", + "MacAlgorithm": "HMAC_SHA_512", + "MacValid": true, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "describe-key-after-deleted-import": { + "KeyMetadata": { + "AWSAccountId": "111111111111", + "Arn": "arn::kms::111111111111:key/", + "CreationDate": "datetime", + "CustomerMasterKeySpec": "HMAC_512", + "Description": "test import hmac key", + "Enabled": false, + "KeyId": "", + "KeyManager": "CUSTOMER", + "KeySpec": "HMAC_512", + "KeyState": "PendingImport", + "KeyUsage": "GENERATE_VERIFY_MAC", + "MacAlgorithms": [ + "HMAC_SHA_512" + ], + "MultiRegion": false, + "Origin": "EXTERNAL" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/kms/test_kms.validation.json b/tests/aws/services/kms/test_kms.validation.json index 7f7708a4186e6..7d53da7de6f49 100644 --- a/tests/aws/services/kms/test_kms.validation.json +++ b/tests/aws/services/kms/test_kms.validation.json @@ -50,6 +50,15 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_and_list_sign_key": { "last_validated_date": "2024-04-11T15:53:27+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_describe_with_alias_arn": { + "last_validated_date": "2025-08-05T17:00:25+00:00", + "durations_in_seconds": { + "setup": 0.83, + "call": 0.99, + "teardown": 0.16, + "total": 1.98 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_disable_and_enable_key": { "last_validated_date": "2024-04-11T15:52:38+00:00" }, @@ -152,6 +161,78 @@ "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_asymmetric": { "last_validated_date": "2024-04-11T15:53:35+00:00" }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P256]": { + "last_validated_date": "2025-08-04T08:10:22+00:00", + "durations_in_seconds": { + "setup": 1.37, + "call": 4.01, + "teardown": 0.32, + "total": 5.7 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P384]": { + "last_validated_date": "2025-08-04T08:10:26+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.15, + "teardown": 0.31, + "total": 3.46 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_NIST_P521]": { + "last_validated_date": "2025-08-04T08:10:29+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.18, + "teardown": 0.31, + "total": 3.49 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_ecc_keys[ECC_SECG_P256K1]": { + "last_validated_date": "2025-08-04T08:10:33+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 3.28, + "teardown": 0.32, + "total": 3.6 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_224]": { + "last_validated_date": "2025-08-04T08:11:28+00:00", + "durations_in_seconds": { + "setup": 1.33, + "call": 3.61, + "teardown": 0.32, + "total": 5.26 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_256]": { + "last_validated_date": "2025-08-04T08:11:32+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.85, + "teardown": 0.32, + "total": 3.17 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_384]": { + "last_validated_date": "2025-08-04T08:11:35+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.89, + "teardown": 0.32, + "total": 3.21 + } + }, + "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_hmac_keys[HMAC_512]": { + "last_validated_date": "2025-08-04T08:11:38+00:00", + "durations_in_seconds": { + "setup": 0.0, + "call": 2.92, + "teardown": 0.32, + "total": 3.24 + } + }, "tests/aws/services/kms/test_kms.py::TestKMS::test_import_key_rsa_aes_wrap_sha256": { "last_validated_date": "2025-07-22T06:11:13+00:00", "durations_in_seconds": { diff --git a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py index fed8e9c4a8723..e09b430e37002 100644 --- a/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py +++ b/tests/aws/services/lambda_/event_source_mapping/test_lambda_integration_dynamodbstreams.py @@ -219,11 +219,11 @@ def test_duplicate_event_source_mappings( )["TableDescription"]["LatestStreamArn"] # extra arguments for create_event_source_mapping calls - kwargs = dict( - StartingPosition="TRIM_HORIZON", - MaximumBatchingWindowInSeconds=1, - MaximumRetryAttempts=1, - ) + kwargs = { + "StartingPosition": "TRIM_HORIZON", + "MaximumBatchingWindowInSeconds": 1, + "MaximumRetryAttempts": 1, + } create_lambda_function( handler_file=TEST_LAMBDA_PYTHON_ECHO, diff --git a/tests/aws/services/lambda_/functions/lambda_response_streaming.js b/tests/aws/services/lambda_/functions/lambda_response_streaming.js new file mode 100644 index 0000000000000..b60c70310d47d --- /dev/null +++ b/tests/aws/services/lambda_/functions/lambda_response_streaming.js @@ -0,0 +1,17 @@ +exports.handler = awslambda.streamifyResponse( + async (event, responseStream, context) => { + const metadata = { + status: 200, + headers: { + 'Content-Type': 'text/html', + 'Cache-Control': 'no-cache', + }, + } + + responseStream = awslambda.HttpResponseStream.from(responseStream, metadata) + + responseStream.write('Hello,'); + responseStream.write(' world!'); + responseStream.end(); + } +); diff --git a/tests/aws/services/lambda_/test_lambda.py b/tests/aws/services/lambda_/test_lambda.py index 08d8d77f0e4f2..4ca943a7da49f 100644 --- a/tests/aws/services/lambda_/test_lambda.py +++ b/tests/aws/services/lambda_/test_lambda.py @@ -12,7 +12,7 @@ import time from concurrent.futures import ThreadPoolExecutor from io import BytesIO -from typing import Dict, TypeVar +from typing import TypeVar import pytest import requests @@ -130,6 +130,7 @@ TEST_LAMBDA_CLOUDWATCH_LOGS = os.path.join(THIS_FOLDER, "functions/lambda_cloudwatch_logs.py") TEST_LAMBDA_XRAY_TRACEID = os.path.join(THIS_FOLDER, "functions/xray_tracing_traceid.py") TEST_LAMBDA_HOST_PREFIX_OPERATION = os.path.join(THIS_FOLDER, "functions/host_prefix_operation.py") +TEST_LAMBDA_RESPONSE_STREAMING = os.path.join(THIS_FOLDER, "functions/lambda_response_streaming.js") PYTHON_TEST_RUNTIMES = RUNTIMES_AGGREGATED["python"] NODE_TEST_RUNTIMES = RUNTIMES_AGGREGATED["nodejs"] @@ -152,7 +153,7 @@ def read_streams(payload: T) -> T: new_payload = {} for k, v in payload.items(): - if isinstance(v, Dict): + if isinstance(v, dict): new_payload[k] = read_streams(v) elif isinstance(v, StreamingBody): new_payload[k] = to_str(v.read()) @@ -396,7 +397,7 @@ def _invoke_lambda(*args): ) return json.load(result["Payload"])["environment"] - def _transform_to_key_dict(env: Dict[str, str]): + def _transform_to_key_dict(env: dict[str, str]): return { "AccessKeyId": env["AWS_ACCESS_KEY_ID"], "SecretAccessKey": env["AWS_SECRET_ACCESS_KEY"], @@ -951,6 +952,32 @@ def test_lambda_url_invocation(self, create_lambda_function, snapshot, returnval }, ) + @markers.aws.validated + def test_function_url_with_response_streaming( + self, aws_client, create_lambda_function, snapshot + ): + function_name = f"test_lambda_{short_uid()}" + create_lambda_function( + func_name=function_name, + handler_file=TEST_LAMBDA_RESPONSE_STREAMING, + handler="lambda_response_streaming.handler", + runtime=Runtime.nodejs18_x, + ) + url_config = aws_client.lambda_.create_function_url_config( + FunctionName=function_name, AuthType="NONE", InvokeMode="RESPONSE_STREAM" + ) + aws_client.lambda_.add_permission( + FunctionName=function_name, + StatementId=str(short_uid()), + Action="lambda:InvokeFunctionUrl", + Principal="*", + FunctionUrlAuthType="NONE", + ) + + result = safe_requests.post(url_config["FunctionUrl"], data=b"{'key':'value'}") + snapshot.match("response_status", result.status_code) + snapshot.match("response_data", result.text) + @markers.aws.validated def test_lambda_url_invocation_custom_id(self, create_lambda_function, aws_client): function_name = f"test-function-{short_uid()}" diff --git a/tests/aws/services/lambda_/test_lambda.snapshot.json b/tests/aws/services/lambda_/test_lambda.snapshot.json index 121d9b01ef397..7bfb46a72e5b8 100644 --- a/tests/aws/services/lambda_/test_lambda.snapshot.json +++ b/tests/aws/services/lambda_/test_lambda.snapshot.json @@ -4604,5 +4604,12 @@ } } } + }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_function_url_with_response_streaming": { + "recorded-date": "30-07-2025, 20:58:56", + "recorded-content": { + "response_status": 200, + "response_data": "Hello, world!" + } } } diff --git a/tests/aws/services/lambda_/test_lambda.validation.json b/tests/aws/services/lambda_/test_lambda.validation.json index 9b5d816f5ac1e..764252f6734aa 100644 --- a/tests/aws/services/lambda_/test_lambda.validation.json +++ b/tests/aws/services/lambda_/test_lambda.validation.json @@ -203,6 +203,15 @@ "tests/aws/services/lambda_/test_lambda.py::TestLambdaPermissions::test_lambda_permission_url_invocation": { "last_validated_date": "2024-04-08T16:57:37+00:00" }, + "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_function_url_with_response_streaming": { + "last_validated_date": "2025-07-30T21:16:00+00:00", + "durations_in_seconds": { + "setup": 10.91, + "call": 3.45, + "teardown": 0.83, + "total": 15.19 + } + }, "tests/aws/services/lambda_/test_lambda.py::TestLambdaURL::test_lambda_update_function_url_config": { "last_validated_date": "2024-04-08T16:57:17+00:00" }, diff --git a/tests/aws/services/lambda_/test_lambda_api.py b/tests/aws/services/lambda_/test_lambda_api.py index 67dc8da6be851..00ceeae9ec812 100644 --- a/tests/aws/services/lambda_/test_lambda_api.py +++ b/tests/aws/services/lambda_/test_lambda_api.py @@ -15,10 +15,10 @@ import logging import re import threading +from collections.abc import Callable from hashlib import sha256 from io import BytesIO from random import randint -from typing import Callable import pytest import requests @@ -4908,7 +4908,7 @@ def _assert_create_aliased_function_url(fn_version: str, fn_alias: str): class TestLambdaSizeLimits: def _generate_sized_python_str(self, filepath: str, size: int) -> str: """Generate a text of the specified size by appending #s at the end of the file""" - with open(filepath, "r") as f: + with open(filepath) as f: py_str = f.read() py_str += "#" * (size - len(py_str)) return py_str diff --git a/tests/aws/services/lambda_/test_lambda_developer_tools.py b/tests/aws/services/lambda_/test_lambda_developer_tools.py index cd637ef2c26e6..ac7f0049dad11 100644 --- a/tests/aws/services/lambda_/test_lambda_developer_tools.py +++ b/tests/aws/services/lambda_/test_lambda_developer_tools.py @@ -55,7 +55,7 @@ def test_hot_reloading( mkdir(hot_reloading_dir_path) cleanups.append(lambda: rm_rf(hot_reloading_dir_path)) function_content = load_file(handler_file) - with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="w") as f: f.write(function_content) mount_path = get_host_path_for_path_in_docker(hot_reloading_dir_path) @@ -74,7 +74,7 @@ def test_hot_reloading( response_dict = json.load(response["Payload"]) assert response_dict["counter"] == 2 assert response_dict["constant"] == "value1" - with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, handler_filename), mode="w") as f: f.write(function_content.replace("value1", "value2")) # we have to sleep here, since the hot reloading is debounced with 500ms time.sleep(sleep_time) @@ -97,7 +97,7 @@ def test_hot_reloading( assert response_dict["counter"] == 1 assert response_dict["constant"] == "value2" # now writing something in the new folder to check if it will reload - with open(os.path.join(test_folder, "test-file"), mode="wt") as f: + with open(os.path.join(test_folder, "test-file"), mode="w") as f: f.write("test-content") time.sleep(sleep_time) response = aws_client.lambda_.invoke(FunctionName=function_name, Payload=b"{}") @@ -122,7 +122,7 @@ def test_hot_reloading_publish_version( mkdir(hot_reloading_dir_path) cleanups.append(lambda: rm_rf(hot_reloading_dir_path)) function_content = load_file(HOT_RELOADING_NODEJS_HANDLER) - with open(os.path.join(hot_reloading_dir_path, "handler.mjs"), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, "handler.mjs"), mode="w") as f: f.write(function_content) mount_path = get_host_path_for_path_in_docker(hot_reloading_dir_path) @@ -167,7 +167,7 @@ def test_hot_reloading_environment_placeholder( mkdir(hot_reloading_dir_path) cleanups.append(lambda: rm_rf(hot_reloading_dir_path)) function_content = load_file(HOT_RELOADING_PYTHON_HANDLER) - with open(os.path.join(hot_reloading_dir_path, "handler.py"), mode="wt") as f: + with open(os.path.join(hot_reloading_dir_path, "handler.py"), mode="w") as f: f.write(function_content) mount_path = get_host_path_for_path_in_docker(hot_reloading_dir_path) diff --git a/tests/aws/services/lambda_/test_lambda_runtimes.py b/tests/aws/services/lambda_/test_lambda_runtimes.py index 6c0e82bbec038..6b3f0fc504f44 100644 --- a/tests/aws/services/lambda_/test_lambda_runtimes.py +++ b/tests/aws/services/lambda_/test_lambda_runtimes.py @@ -7,7 +7,6 @@ import os import shutil import textwrap -from typing import List import pytest @@ -45,7 +44,7 @@ class LambdaJavaTestlibsPackage(Package): def __init__(self): super().__init__("JavaLambdaTestlibs", LOCALSTACK_MAVEN_VERSION) - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return [LOCALSTACK_MAVEN_VERSION] def _get_installer(self, version: str) -> PackageInstaller: diff --git a/tests/aws/services/s3/test_s3.py b/tests/aws/services/s3/test_s3.py index 2907c3225326e..94ea9d06312ff 100644 --- a/tests/aws/services/s3/test_s3.py +++ b/tests/aws/services/s3/test_s3.py @@ -3713,9 +3713,9 @@ def test_s3_invalid_content_md5(self, s3_bucket, snapshot, aws_client): base_64_content_md5 = etag_to_base_64_content_md5(response["ETag"]) assert content_md5 == base_64_content_md5 - bad_digest_md5 = base64.b64encode( - hashlib.md5(f"{content}1".encode("utf-8")).digest() - ).decode("utf-8") + bad_digest_md5 = base64.b64encode(hashlib.md5(f"{content}1".encode()).digest()).decode( + "utf-8" + ) hashes = [ "__invalid__", diff --git a/tests/aws/services/s3/test_s3_notifications_sns.py b/tests/aws/services/s3/test_s3_notifications_sns.py index 81b6b2bd675dd..939838db68966 100644 --- a/tests/aws/services/s3/test_s3_notifications_sns.py +++ b/tests/aws/services/s3/test_s3_notifications_sns.py @@ -1,7 +1,7 @@ import json import logging from io import BytesIO -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING import pytest from botocore.exceptions import ClientError @@ -26,7 +26,7 @@ def create_sns_bucket_notification( sns_client: "SNSClient", bucket_name: str, topic_arn: str, - events: List["EventType"], + events: list["EventType"], ): """A NotificationFactory.""" bucket_arn = arns.s3_bucket_arn(bucket_name) @@ -49,20 +49,20 @@ def create_sns_bucket_notification( s3_client.put_bucket_notification_configuration( Bucket=bucket_name, - NotificationConfiguration=dict( - TopicConfigurations=[ - dict( - TopicArn=topic_arn, - Events=events, - ) + NotificationConfiguration={ + "TopicConfigurations": [ + { + "TopicArn": topic_arn, + "Events": events, + } ] - ), + }, ) def sqs_collect_sns_messages( sqs_client: "SQSClient", queue_url: str, min_messages: int, timeout: int = 10 -) -> List[Dict]: +) -> list[dict]: """ Polls the given queue for the given amount of time and extracts the received SQS messages all SNS messages (messages that have a "TopicArn" field). diff --git a/tests/aws/services/s3/test_s3_notifications_sqs.py b/tests/aws/services/s3/test_s3_notifications_sqs.py index 498c589a7150c..2c04d42aec05c 100644 --- a/tests/aws/services/s3/test_s3_notifications_sqs.py +++ b/tests/aws/services/s3/test_s3_notifications_sqs.py @@ -1,7 +1,7 @@ import json import logging from io import BytesIO -from typing import TYPE_CHECKING, Dict, List, Protocol +from typing import TYPE_CHECKING, Protocol import pytest import requests @@ -29,7 +29,7 @@ class NotificationFactory(Protocol): A protocol for connecting a bucket to a queue with a notification configurations and the necessary policies. """ - def __call__(self, bucket_name: str, queue_url: str, events: List["EventType"]) -> None: + def __call__(self, bucket_name: str, queue_url: str, events: list["EventType"]) -> None: """ Creates a new notification configuration and respective policies. @@ -79,15 +79,15 @@ def create_sqs_bucket_notification( sqs_client: "SQSClient", bucket_name: str, queue_url: str, - events: List["EventType"], + events: list["EventType"], ): """A NotificationFactory.""" queue_arn = set_policy_for_queue(sqs_client, queue_url, bucket_name) s3_client.put_bucket_notification_configuration( Bucket=bucket_name, - NotificationConfiguration=dict( - QueueConfigurations=[dict(QueueArn=queue_arn, Events=events)] - ), + NotificationConfiguration={ + "QueueConfigurations": [{"QueueArn": queue_arn, "Events": events}] + }, ) @@ -100,7 +100,7 @@ def s3_create_sqs_bucket_notification(aws_client) -> NotificationFactory: def factory( bucket_name: str, queue_url: str, - events: List["EventType"], + events: list["EventType"], s3_client=aws_client.s3, sqs_client=aws_client.sqs, ): @@ -111,7 +111,7 @@ def factory( def sqs_collect_s3_events( sqs_client: "SQSClient", queue_url: str, min_events: int, timeout: int = 10 -) -> List[Dict]: +) -> list[dict]: """ Polls the given queue for the given amount of time and extracts and flattens from the received messages all events (messages that have a "Records" field in their body, and where the records can be json-deserialized). diff --git a/tests/aws/services/secretsmanager/test_secretsmanager.py b/tests/aws/services/secretsmanager/test_secretsmanager.py index 7a91414c6879e..9fdf54e21496f 100644 --- a/tests/aws/services/secretsmanager/test_secretsmanager.py +++ b/tests/aws/services/secretsmanager/test_secretsmanager.py @@ -5,7 +5,6 @@ import uuid from datetime import datetime from math import isclose -from typing import Optional import pytest import requests @@ -163,7 +162,7 @@ def _is_secret_deleted(): def _wait_rotation(client, secret_id: str, secret_version: str): def _is_secret_rotated(): resp: dict = client.describe_secret(SecretId=secret_id) - secret_stage_tags = list() + secret_stage_tags = [] for key, tags in resp.get("VersionIdsToStages", {}).items(): if key == secret_version: secret_stage_tags = tags @@ -479,7 +478,7 @@ def test_create_multi_secrets(self, cleanups, aws_client): ) rs = aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description="testing creation of secrets", ) arns.append(rs["ARN"]) @@ -750,7 +749,7 @@ def test_put_secret_value_with_version_stages(self, sm_snapshot, secret_name, aw sm_snapshot.match("get_secret_value_res_2", get_secret_value_res_2) secret_string_v3: str = "secret_string_v3" - version_stages_v3: ["str"] = ["AWSPENDING"] + version_stages_v3: [str] = ["AWSPENDING"] pv_v3_vid: str = str(uuid.uuid4()) # put_secret_value_res_3 = aws_client.secretsmanager.put_secret_value( @@ -1747,7 +1746,7 @@ def secretsmanager_http_put_pending_secret_value_val_res( return TestSecretsManager.secretsmanager_http_put_secret_value_val_res(res, secret_name) def secretsmanager_http_put_secret_value_with( - self, secret_id: str, secret_string: str, client_request_token: Optional[str] + self, secret_id: str, secret_string: str, client_request_token: str | None ) -> requests.Response: http_body: json = { "SecretId": secret_id, @@ -1767,7 +1766,7 @@ def secretsmanager_http_put_secret_value_with_val_res( return res_json def secretsmanager_http_update_secret( - self, secret_id: str, secret_string: str, client_request_token: Optional[str] + self, secret_id: str, secret_string: str, client_request_token: str | None ): http_body: json = {"SecretId": secret_id, "SecretString": secret_string} if client_request_token: @@ -1776,7 +1775,7 @@ def secretsmanager_http_update_secret( @staticmethod def secretsmanager_http_update_secret_val_res( - res: requests.Response, secret_name: str, client_request_token: Optional[str] + res: requests.Response, secret_name: str, client_request_token: str | None ): assert res.status_code == 200 res_json: json = res.json() @@ -1789,7 +1788,7 @@ def secretsmanager_http_put_secret_value_with_version( self, secret_id: str, secret_string: str, - client_request_token: Optional[str], + client_request_token: str | None, version_stages: list[str], ) -> requests.Response: http_body: json = { @@ -1804,7 +1803,7 @@ def secretsmanager_http_put_secret_value_with_version( def secretsmanager_http_put_secret_value_with_version_val_res( res: requests.Response, secret_name: str, - client_request_token: Optional[str], + client_request_token: str | None, version_stages: list[str], ) -> json: req_version_id: str @@ -2270,7 +2269,7 @@ def test_secret_exists(self, secret_name, aws_client): description = "Testing secret already exists." rs = aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) self._wait_created_is_listed(aws_client.secretsmanager, secret_id=secret_name) @@ -2292,7 +2291,7 @@ def test_secret_exists(self, secret_name, aws_client): ) as res_exists_ex: aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) assert res_exists_ex.typename == "ResourceExistsException" @@ -2307,7 +2306,7 @@ def test_secret_exists_snapshots(self, secret_name, sm_snapshot, cleanups, aws_c description = "Snapshot testing secret already exists." rs = aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) self._wait_created_is_listed(aws_client.secretsmanager, secret_id=secret_name) @@ -2318,7 +2317,7 @@ def test_secret_exists_snapshots(self, secret_name, sm_snapshot, cleanups, aws_c ) as res_exists_ex: aws_client.secretsmanager.create_secret( Name=secret_name, - SecretString="my_secret_{}".format(secret_name), + SecretString=f"my_secret_{secret_name}", Description=description, ) sm_snapshot.match("ex_log", res_exists_ex.value.response) diff --git a/tests/aws/services/ses/test_ses.py b/tests/aws/services/ses/test_ses.py index 126edfc717ded..6220480f77715 100644 --- a/tests/aws/services/ses/test_ses.py +++ b/tests/aws/services/ses/test_ses.py @@ -2,7 +2,6 @@ import json import os from datetime import date, datetime -from typing import Optional, Tuple import pytest import requests @@ -13,7 +12,7 @@ from localstack.testing.aws.util import is_aws_cloud from localstack.testing.pytest import markers from localstack.utils.strings import short_uid -from localstack.utils.sync import retry +from localstack.utils.sync import poll_condition, retry SAMPLE_TEMPLATE = { "TemplateName": "hello-world", @@ -93,8 +92,8 @@ def setup_email_addresses(ses_verify_identity): """ def inner( - sender_email_address: Optional[str] = None, recipient_email_address: Optional[str] = None - ) -> Tuple[str, str]: + sender_email_address: str | None = None, recipient_email_address: str | None = None + ) -> tuple[str, str]: if is_aws_cloud(): if sender_email_address is None: raise ValueError( @@ -391,6 +390,75 @@ def test_clone_receipt_rule_set( assert cloned_rule_set["Rules"] == original_rule_set["Rules"] assert [x["Name"] for x in cloned_rule_set["Rules"]] == rule_names + @markers.aws.validated + @pytest.mark.parametrize("notification_type", ["Bounce", "Complaint", "Delivery"]) + @pytest.mark.parametrize("enabled", [True, False]) + def test_set_identity_headers_in_notifications_enabled_success( + self, aws_client, setup_email_addresses, snapshot, notification_type, enabled + ): + """ + Test SetIdentityHeadersInNotificationsEnabled for valid identities and notification types. + Also checks idempotency. + """ + sender_email, _ = setup_email_addresses() + + response = aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=sender_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match(f"set-headers-{notification_type.lower()}-enabled-{enabled}", response) + + # Idempotency check + response2 = aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=sender_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match( + f"set-headers-{notification_type.lower()}-enabled-{enabled}-idempotent", response2 + ) + + @markers.aws.validated + def test_set_identity_headers_in_notifications_enabled_failure_invalid_type( + self, aws_client, setup_email_addresses, snapshot + ): + """ + Test SetIdentityHeadersInNotificationsEnabled for invalid notification types. + """ + sender_email, _ = setup_email_addresses() + enabled = True + notification_type = "InvalidType" + + with pytest.raises(ClientError) as exc: + aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=sender_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match("set-headers-error-invalidtype", exc.value.response) + + @markers.aws.validated + @pytest.mark.parametrize("notification_type", ["Bounce", "Complaint", "Delivery"]) + def test_set_identity_headers_in_notifications_enabled_failure_unknown_identity( + self, aws_client, snapshot, notification_type + ): + """ + Test SetIdentityHeadersInNotificationsEnabled for unknown identity. + """ + enabled = True + unknown_email = "unknown@example.com" + + with pytest.raises(ClientError) as exc: + aws_client.ses.set_identity_headers_in_notifications_enabled( + Identity=unknown_email, + NotificationType=notification_type, + Enabled=enabled, + ) + snapshot.match( + f"set-headers-error-unknown-identity-{notification_type.lower()}", exc.value.response + ) + @markers.aws.manual_setup_required @markers.snapshot.skip_snapshot_verify( paths=[ @@ -850,6 +918,87 @@ def test_special_tags_send_email(self, tag_name, tag_value, aws_client): assert exc.match("MessageRejected") + # we cannot really introspect received emails in AWS + @markers.aws.only_localstack + def test_ses_sns_topic_integration_send_email_ses_destination( + self, + sns_topic, + sns_subscription, + ses_configuration_set, + ses_configuration_set_sns_event_destination, + setup_email_addresses, + aws_client, + ): + """ + Validates that configure Event Destinations and sending an email does not trigger an infinite loop of sending + SNS notifications that sends an email that would trigger SNS. + """ + + sender_email_address, recipient_email_address = setup_email_addresses() + config_set_name = f"config-set-{short_uid()}" + + emails_url = config.internal_service_url() + EMAILS_ENDPOINT + response = requests.delete(emails_url) + assert response.status_code == 204 + + # create subscription to get notified about SES events + topic_arn = sns_topic["Attributes"]["TopicArn"] + sns_subscription( + TopicArn=topic_arn, + Protocol="email", + Endpoint=sender_email_address, + ) + + # create the config set + ses_configuration_set(config_set_name) + event_destination_name = f"config-set-event-destination-{short_uid()}" + ses_configuration_set_sns_event_destination( + config_set_name, event_destination_name, topic_arn + ) + + # send an email to trigger the SNS message and SES message + destination = { + "ToAddresses": [recipient_email_address], + } + send_email = aws_client.ses.send_email( + Destination=destination, + Message=SAMPLE_SIMPLE_EMAIL, + ConfigurationSetName=config_set_name, + Source=sender_email_address, + Tags=[ + { + "Name": "custom-tag", + "Value": "tag-value", + } + ], + ) + + def _get_emails(): + _resp = requests.get(emails_url) + return _resp.json()["messages"] + + poll_condition(lambda: len(_get_emails()) >= 4, timeout=3) + requests.delete(emails_url, params={"id": send_email["MessageId"]}) + + emails = _get_emails() + # we assert that we only received 3 emails + assert len(emails) == 3 + + emails = sorted(emails, key=lambda x: x["Body"]["text_part"]) + # the first email is the validation of SNS confirming the SES subscription + ses_delivery_notification = emails[1] + ses_send_notification = emails[2] + + assert ses_delivery_notification["Subject"] == "SNS-Subscriber-Endpoint" + delivery_payload = json.loads(ses_delivery_notification["Body"]["text_part"]) + assert delivery_payload["eventType"] == "Delivery" + assert delivery_payload["mail"]["source"] == sender_email_address + + assert ses_send_notification["Subject"] == "SNS-Subscriber-Endpoint" + send_payload = json.loads(ses_send_notification["Body"]["text_part"]) + assert send_payload["eventType"] == "Send" + assert send_payload["mail"]["source"] == sender_email_address + @pytest.mark.usefixtures("openapi_validate") class TestSESRetrospection: @@ -863,7 +1012,7 @@ def test_send_email_can_retrospect(self, aws_client): def _read_message_from_filesystem(message_id: str) -> dict: """Given a message ID, read the message from filesystem and deserialise it.""" data_dir = config.dirs.data or config.dirs.tmp - with open(os.path.join(data_dir, "ses", message_id + ".json"), "r") as f: + with open(os.path.join(data_dir, "ses", message_id + ".json")) as f: message = f.read() return json.loads(message) @@ -956,7 +1105,7 @@ def test_send_templated_email_can_retrospect(self, create_template, aws_client): ) message_id = message["MessageId"] - with open(os.path.join(data_dir, "ses", message_id + ".json"), "r") as f: + with open(os.path.join(data_dir, "ses", message_id + ".json")) as f: message = f.read() contents = json.loads(message) diff --git a/tests/aws/services/ses/test_ses.snapshot.json b/tests/aws/services/ses/test_ses.snapshot.json index 73336d13e1921..83af3b68b959e 100644 --- a/tests/aws/services/ses/test_ses.snapshot.json +++ b/tests/aws/services/ses/test_ses.snapshot.json @@ -915,5 +915,171 @@ } } } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Bounce]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-bounce-enabled-True": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-bounce-enabled-True-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Complaint]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-complaint-enabled-True": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-complaint-enabled-True-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[True-Delivery]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-delivery-enabled-True": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-delivery-enabled-True-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Bounce]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-bounce-enabled-False": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-bounce-enabled-False-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Complaint]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-complaint-enabled-False": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-complaint-enabled-False-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_success[False-Delivery]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-delivery-enabled-False": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "set-headers-delivery-enabled-False-idempotent": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_invalid_type": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-invalidtype": { + "Error": { + "Code": "InvalidParameterValue", + "Message": "Invalid notification type: InvalidType. Valid values are: Bounce, Complaint, Delivery.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Bounce]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-unknown-identity-bounce": { + "Error": { + "Code": "MessageRejected", + "Message": "Identity unknown@example.com is not verified or does not exist.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Complaint]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-unknown-identity-complaint": { + "Error": { + "Code": "MessageRejected", + "Message": "Identity unknown@example.com is not verified or does not exist.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } + }, + "tests/aws/services/ses/test_ses.py::TestSES::test_set_identity_headers_in_notifications_enabled_failure_unknown_identity[Delivery]": { + "recorded-date": "29-07-2025, 13:55:22", + "recorded-content": { + "set-headers-error-unknown-identity-delivery": { + "Error": { + "Code": "MessageRejected", + "Message": "Identity unknown@example.com is not verified or does not exist.", + "Type": "Sender" + }, + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 400 + } + } + } } } diff --git a/tests/aws/services/sqs/test_sqs.py b/tests/aws/services/sqs/test_sqs.py index 48bbc031ffb2e..959417eb628b7 100644 --- a/tests/aws/services/sqs/test_sqs.py +++ b/tests/aws/services/sqs/test_sqs.py @@ -4,7 +4,7 @@ import time from queue import Empty, Queue from threading import Timer -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING import pytest import requests @@ -2117,6 +2117,42 @@ def test_fifo_queue_send_message_with_delay_on_queue_works( assert messages[1]["Body"] == "message-2" assert messages[2]["Body"] == "message-3" + @markers.aws.validated + def test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay( + self, sqs_create_queue, aws_sqs_client, snapshot + ): + delay_seconds = 2 + queue_url = sqs_create_queue( + QueueName=f"queue-{short_uid()}.fifo", + Attributes={ + "FifoQueue": "true", + "ContentBasedDeduplication": "true", + "DelaySeconds": str(delay_seconds), + }, + ) + + send_result = aws_sqs_client.send_message( + QueueUrl=queue_url, MessageBody="message-1", MessageGroupId="1", DelaySeconds=0 + ) + snapshot.match("send_message_result", send_result) + + response_initial_receive = aws_sqs_client.receive_message( + QueueUrl=queue_url, WaitTimeSeconds=1 + ) + snapshot.match("receive_message_initial_result", response_initial_receive) + assert response_initial_receive.get("Messages", []) == [] + + time.sleep(delay_seconds + 1) + + response_after_delay = aws_sqs_client.receive_message( + QueueUrl=queue_url, MaxNumberOfMessages=1 + ) + snapshot.match("receive_message_after_delay_result", response_after_delay) + messages = response_after_delay["Messages"] + assert len(messages) == 1 + + assert messages[0]["Body"] == "message-1" + @markers.aws.validated def test_fifo_message_attributes(self, sqs_create_queue, snapshot, aws_sqs_client): snapshot.add_transformer(snapshot.transform.sqs_api()) @@ -2453,7 +2489,7 @@ def test_publish_get_delete_message_batch(self, sqs_create_queue, aws_sqs_client ids_received = set() for i in range(message_count): ids_sent.add(successful[i]["MessageId"]) - ids_received.add((result_recv[i]["MessageId"])) + ids_received.add(result_recv[i]["MessageId"]) assert ids_sent == ids_received @@ -2965,9 +3001,7 @@ def test_dead_letter_queue_config(self, sqs_create_queue, region_name): dl_queue_url = sqs_create_queue(QueueName=dead_letter_queue_name) url_parts = dl_queue_url.split("/") - dl_target_arn = "arn:aws:sqs:{}:{}:{}".format( - region_name, url_parts[len(url_parts) - 2], url_parts[-1] - ) + dl_target_arn = f"arn:aws:sqs:{region_name}:{url_parts[len(url_parts) - 2]}:{url_parts[-1]}" conf = {"deadLetterTargetArn": dl_target_arn, "maxReceiveCount": 50} attributes = {"RedrivePolicy": json.dumps(conf)} @@ -3735,21 +3769,19 @@ def test_dead_letter_queue_execution_lambda_mapping_preserves_id( # FIXME: message id is now preserved, but test is broken # https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html queue_name = f"queue-{short_uid()}" - dead_letter_queue_name = "dl-queue-{}".format(short_uid()) + dead_letter_queue_name = f"dl-queue-{short_uid()}" dl_queue_url = sqs_create_queue(QueueName=dead_letter_queue_name) # create arn url_parts = dl_queue_url.split("/") - dl_target_arn = "arn:aws:sqs:{}:{}:{}".format( - region_name, url_parts[len(url_parts) - 2], url_parts[-1] - ) + dl_target_arn = f"arn:aws:sqs:{region_name}:{url_parts[len(url_parts) - 2]}:{url_parts[-1]}" policy = {"deadLetterTargetArn": dl_target_arn, "maxReceiveCount": 1} queue_url = sqs_create_queue( QueueName=queue_name, Attributes={"RedrivePolicy": json.dumps(policy)} ) - lambda_name = "lambda-{}".format(short_uid()) + lambda_name = f"lambda-{short_uid()}" create_lambda_function( func_name=lambda_name, handler_file=TEST_LAMBDA_PYTHON, @@ -3757,9 +3789,7 @@ def test_dead_letter_queue_execution_lambda_mapping_preserves_id( ) # create arn url_parts = queue_url.split("/") - queue_arn = "arn:aws:sqs:{}:{}:{}".format( - region_name, url_parts[len(url_parts) - 2], url_parts[-1] - ) + queue_arn = f"arn:aws:sqs:{region_name}:{url_parts[len(url_parts) - 2]}:{url_parts[-1]}" aws_client.lambda_.create_event_source_mapping( EventSourceArn=queue_arn, FunctionName=lambda_name ) @@ -4622,7 +4652,7 @@ def test_delete_message_with_deleted_receipt_handle(self, sqs_queue, aws_sqs_cli def _add_error_detail_transformer(self, snapshot): """Adds a transformer to ignore {"Error": {"Detail": None, ...}} entries in snapshot error responses""" - def _remove_error_details(snapshot_content: Dict, *args) -> Dict: + def _remove_error_details(snapshot_content: dict, *args) -> dict: for response in snapshot_content.values(): response.get("Error", {}).pop("Detail", None) return snapshot_content diff --git a/tests/aws/services/sqs/test_sqs.snapshot.json b/tests/aws/services/sqs/test_sqs.snapshot.json index f98d9586d459b..a12bcd66e4d94 100644 --- a/tests/aws/services/sqs/test_sqs.snapshot.json +++ b/tests/aws/services/sqs/test_sqs.snapshot.json @@ -4093,5 +4093,73 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_message_deduplication_id_success": { "recorded-date": "30-07-2025, 10:26:48", "recorded-content": {} + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs]": { + "recorded-date": "31-07-2025, 09:53:01", + "recorded-content": { + "send_message_result": { + "MD5OfMessageBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "SequenceNumber": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_initial_result": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_after_delay_result": { + "Messages": [ + { + "Body": "message-1", + "MD5OfBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs_query]": { + "recorded-date": "31-07-2025, 09:53:08", + "recorded-content": { + "send_message_result": { + "MD5OfMessageBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "SequenceNumber": "", + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_initial_result": { + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + }, + "receive_message_after_delay_result": { + "Messages": [ + { + "Body": "message-1", + "MD5OfBody": "3d6b824fd8c1520e9a047d21fee6fb1f", + "MessageId": "", + "ReceiptHandle": "" + } + ], + "ResponseMetadata": { + "HTTPHeaders": {}, + "HTTPStatusCode": 200 + } + } + } } } diff --git a/tests/aws/services/sqs/test_sqs.validation.json b/tests/aws/services/sqs/test_sqs.validation.json index f642b281a5b11..dfb1743d126f1 100644 --- a/tests/aws/services/sqs/test_sqs.validation.json +++ b/tests/aws/services/sqs/test_sqs.validation.json @@ -200,6 +200,24 @@ "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_delay_seconds_fails[sqs_query]": { "last_validated_date": "2024-04-30T13:33:34+00:00" }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs]": { + "last_validated_date": "2025-07-31T09:53:01+00:00", + "durations_in_seconds": { + "setup": 5.06, + "call": 6.23, + "teardown": 0.33, + "total": 11.62 + } + }, + "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_queue_send_message_with_zero_delay_defaults_to_queue_delay[sqs_query]": { + "last_validated_date": "2025-07-31T09:53:08+00:00", + "durations_in_seconds": { + "setup": 0.03, + "call": 6.14, + "teardown": 0.34, + "total": 6.51 + } + }, "tests/aws/services/sqs/test_sqs.py::TestSqsProvider::test_fifo_set_content_based_deduplication_strategy[sqs]": { "last_validated_date": "2024-04-30T13:47:39+00:00" }, diff --git a/tests/aws/services/ssm/test_ssm.py b/tests/aws/services/ssm/test_ssm.py index b9a7ff7ff79c5..009e79a252028 100644 --- a/tests/aws/services/ssm/test_ssm.py +++ b/tests/aws/services/ssm/test_ssm.py @@ -176,11 +176,11 @@ def test_trigger_event_on_systems_manager_change( ): monkeypatch.setattr(config, "SQS_ENDPOINT_STRATEGY", strategy) - rule_name = "rule-{}".format(short_uid()) - target_id = "target-{}".format(short_uid()) + rule_name = f"rule-{short_uid()}" + target_id = f"target-{short_uid()}" # create queue - queue_name = "queue-{}".format(short_uid()) + queue_name = f"queue-{short_uid()}" queue_url = aws_client.sqs.create_queue(QueueName=queue_name)["QueueUrl"] queue_arn = arns.sqs_queue_arn(queue_name, TEST_AWS_ACCOUNT_ID, TEST_AWS_REGION_NAME) diff --git a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py index 2ab9f76a13f9a..ec97e7119e3fb 100644 --- a/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py +++ b/tests/aws/services/stepfunctions/mocked_service_integrations/mocked_service_integrations.py @@ -6,7 +6,7 @@ import json5 _THIS_FOLDER: Final[str] = os.path.dirname(os.path.realpath(__file__)) -_LOAD_CACHE: Final[dict[str, dict]] = dict() +_LOAD_CACHE: Final[dict[str, dict]] = {} class MockedServiceIntegrationsLoader(abc.ABC): @@ -52,7 +52,7 @@ class MockedServiceIntegrationsLoader(abc.ABC): def load(file_path: str) -> dict: template = _LOAD_CACHE.get(file_path) if template is None: - with open(file_path, "r") as df: + with open(file_path) as df: template = json5.load(df) _LOAD_CACHE[file_path] = template return copy.deepcopy(template) diff --git a/tests/aws/services/stepfunctions/templates/template_loader.py b/tests/aws/services/stepfunctions/templates/template_loader.py index d7e30ac48f4d8..7e84d95490ff3 100644 --- a/tests/aws/services/stepfunctions/templates/template_loader.py +++ b/tests/aws/services/stepfunctions/templates/template_loader.py @@ -4,7 +4,7 @@ import json5 -_LOAD_CACHE: Final[dict[str, dict]] = dict() +_LOAD_CACHE: Final[dict[str, dict]] = {} class TemplateLoader(abc.ABC): @@ -12,7 +12,7 @@ class TemplateLoader(abc.ABC): def load_sfn_template(file_path: str) -> dict: template = _LOAD_CACHE.get(file_path) if template is None: - with open(file_path, "r") as df: + with open(file_path) as df: template = json5.load(df) _LOAD_CACHE[file_path] = template return copy.deepcopy(template) diff --git a/tests/aws/services/stepfunctions/v2/base/test_base.py b/tests/aws/services/stepfunctions/v2/base/test_base.py index a124678cd42a5..19f219dccd52b 100644 --- a/tests/aws/services/stepfunctions/v2/base/test_base.py +++ b/tests/aws/services/stepfunctions/v2/base/test_base.py @@ -172,7 +172,7 @@ def test_event_bridge_events_base( template = BaseTemplate.load_sfn_template(BaseTemplate.BASE_WAIT_1_MIN) template["States"]["State_1"]["Seconds"] = 60 if is_aws_cloud() else 1 definition = json.dumps(template) - execution_input = json.dumps(dict()) + execution_input = json.dumps({}) create_and_record_events( create_state_machine_iam_role, create_state_machine, diff --git a/tests/aws/services/stepfunctions/v2/base/test_wait.py b/tests/aws/services/stepfunctions/v2/base/test_wait.py index b9c1f4a243b2e..915da792d5360 100644 --- a/tests/aws/services/stepfunctions/v2/base/test_wait.py +++ b/tests/aws/services/stepfunctions/v2/base/test_wait.py @@ -36,9 +36,7 @@ def test_timestamp_too_far_in_future_boundary( template = BaseTemplate.load_sfn_template(BaseTemplate.WAIT_TIMESTAMP_PATH) definition = json.dumps(template) - wait_timestamp = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta( - days=days - ) + wait_timestamp = datetime.datetime.now(tz=datetime.UTC) + datetime.timedelta(days=days) timestamp = wait_timestamp.strftime("%Y-%m-%dT%H:%M:%S") full_timestamp = f"{timestamp}.000Z" @@ -82,7 +80,7 @@ def test_wait_timestamppath( template = BaseTemplate.load_sfn_template(BaseTemplate.WAIT_TIMESTAMP_PATH) definition = json.dumps(template) - wait_timestamp = datetime.datetime.now(tz=datetime.timezone.utc) + wait_timestamp = datetime.datetime.now(tz=datetime.UTC) timestamp = wait_timestamp.strftime("%Y-%m-%dT%H:%M:%S") full_timestamp = f"{timestamp}{timestamp_suffix}" diff --git a/tests/aws/services/stepfunctions/v2/callback/test_callback.py b/tests/aws/services/stepfunctions/v2/callback/test_callback.py index 90879273d2d28..231b4c2a87850 100644 --- a/tests/aws/services/stepfunctions/v2/callback/test_callback.py +++ b/tests/aws/services/stepfunctions/v2/callback/test_callback.py @@ -606,7 +606,7 @@ def test_multiple_executions_and_heartbeat_notifications( # Launch multiple execution of the same state machine. execution_count = 6 - execution_arns = list() + execution_arns = [] for _ in range(execution_count): execution_arn = aws_client.stepfunctions.start_execution( stateMachineArn=state_machine_arn, input=exec_input diff --git a/tests/aws/services/stepfunctions/v2/choice_operators/utils.py b/tests/aws/services/stepfunctions/v2/choice_operators/utils.py index 55d8830cc4c11..b9ecc4c7870f2 100644 --- a/tests/aws/services/stepfunctions/v2/choice_operators/utils.py +++ b/tests/aws/services/stepfunctions/v2/choice_operators/utils.py @@ -71,7 +71,7 @@ def create_and_test_comparison_function( COT.COMPARISON_OPERATOR_PLACEHOLDER, comparison_func_name ) - input_output_cases: list[dict[str, Any]] = list() + input_output_cases: list[dict[str, Any]] = [] for i, (variable, value) in enumerate(comparisons): exec_input = json.dumps({COT.VARIABLE_KEY: variable, COT.VALUE_KEY: value}) diff --git a/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py b/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py index 125016f96ee55..61e906ce4979c 100644 --- a/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py +++ b/tests/aws/services/stepfunctions/v2/error_handling/test_states_errors.py @@ -199,7 +199,7 @@ def test_start_large_input( } definition = json.dumps(template) - exec_input = json.dumps(dict()) + exec_input = json.dumps({}) create_and_record_execution( aws_client, create_state_machine_iam_role, diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py index 6dd745757fb3e..acc85baba95c5 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array.py @@ -37,7 +37,7 @@ def test_array_2( '{"Arg1": 1, "Arg2": []}', json.loads('{"Arg1": 1, "Arg2": []}'), ] - input_values = list() + input_values = [] for value in values: input_values.append({"fst": value, "snd": value}) create_and_test_on_inputs( @@ -54,7 +54,7 @@ def test_array_partition( self, create_state_machine_iam_role, create_state_machine, sfn_snapshot, aws_client ): arrays = [list(range(i)) for i in range(5)] - input_values = list() + input_values = [] for array in arrays: for chunk_size in range(1, 6): input_values.append({"fst": array, "snd": chunk_size}) @@ -82,7 +82,7 @@ def test_array_contains( ([True, False], True), ([True, False], False), ] - input_values = list() + input_values = [] for array, value in search_bindings: input_values.append({"fst": array, "snd": value}) create_and_test_on_inputs( @@ -104,7 +104,7 @@ def test_array_range( (1, 9, 9), (1, 9, 2), ] - input_values = list() + input_values = [] for fst, lst, step in ranges: input_values.append({"fst": fst, "snd": lst, "trd": step}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py index 23d325683e2c7..a7b5da0092211 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_array_jsonata.py @@ -12,7 +12,7 @@ def test_array_partition( ): # TODO: test and add support for raising exception on empty array. arrays = [list(range(i)) for i in range(1, 5)] - input_values = list() + input_values = [] for array in arrays: for chunk_size in range(1, 6): input_values.append({"fst": array, "snd": chunk_size}) @@ -35,7 +35,7 @@ def test_array_range( (1, 9, 9), (1, 9, 2), ] - input_values = list() + input_values = [] for fst, lst, step in ranges: input_values.append({"fst": fst, "snd": lst, "trd": step}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py index b480ecf4725ee..61407ac4cd4d8 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_generic.py @@ -38,7 +38,7 @@ def test_format_2( '{"Arg1": 1, "Arg2": []}', json.loads('{"Arg1": 1, "Arg2": []}'), ] - input_values = list() + input_values = [] for value in values: input_values.append({"fst": value, "snd": value}) diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py index d3b37ad599872..ea9555f3da6d7 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_json_manipulation.py @@ -67,7 +67,7 @@ def test_json_merge( merge_bindings = [ ({"a": {"a1": 1, "a2": 2}, "b": 2, "d": 3}, {"a": {"a3": 1, "a4": 2}, "c": 3, "d": 4}), ] - input_values = list() + input_values = [] for fst, snd in merge_bindings: input_values.append({"fst": fst, "snd": snd}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py index 61d10ce646811..30196d09649c4 100644 --- a/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py +++ b/tests/aws/services/stepfunctions/v2/intrinsic_functions/test_math_operations.py @@ -144,7 +144,7 @@ def test_math_add( (-2.5, 0), # python: -2, # java: -3 (-3.5, 0), # python: -4, # java: -4 ] - input_values = list() + input_values = [] for fst, snd in add_tuples: input_values.append({"fst": fst, "snd": snd}) create_and_test_on_inputs( diff --git a/tests/aws/services/stepfunctions/v2/logs/test_logs.py b/tests/aws/services/stepfunctions/v2/logs/test_logs.py index 0948cdbb54fd5..4855398705062 100644 --- a/tests/aws/services/stepfunctions/v2/logs/test_logs.py +++ b/tests/aws/services/stepfunctions/v2/logs/test_logs.py @@ -270,7 +270,7 @@ def test_log_group_with_multiple_runs( expected_events_count += len(execution_history["events"]) logs_client = aws_client.logs - log_events = list() + log_events = [] def _collect_log_events(): log_streams = logs_client.describe_log_streams(logGroupName=log_group_name)[ diff --git a/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py b/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py index 568ab840aafbb..74cd5bccced5a 100644 --- a/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py +++ b/tests/aws/services/stepfunctions/v2/scenarios/test_base_scenarios.py @@ -122,7 +122,7 @@ def test_parallel_state( ) @markers.aws.validated - @pytest.mark.parametrize("max_concurrency_value", [dict(), "NoNumber", 0, 1]) + @pytest.mark.parametrize("max_concurrency_value", [{}, "NoNumber", 0, 1]) def test_max_concurrency_path( self, aws_client, @@ -1285,7 +1285,7 @@ def test_map_state_tolerated_failure_values( ) @markers.aws.validated - @pytest.mark.parametrize("tolerated_failure_count_value", [dict(), "NoNumber", -1, 0, 1]) + @pytest.mark.parametrize("tolerated_failure_count_value", [{}, "NoNumber", -1, 0, 1]) def test_map_state_tolerated_failure_count_path( self, aws_client, @@ -1311,7 +1311,7 @@ def test_map_state_tolerated_failure_count_path( @markers.aws.validated @pytest.mark.parametrize( - "tolerated_failure_percentage_value", [dict(), "NoNumber", -1.1, -1, 0, 1, 1.1, 100, 100.1] + "tolerated_failure_percentage_value", [{}, "NoNumber", -1.1, -1, 0, 1, 1.1, 100, 100.1] ) def test_map_state_tolerated_failure_percentage_path( self, diff --git a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py index 0c30e5af18d58..33148cd988fc0 100644 --- a/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py +++ b/tests/aws/services/stepfunctions/v2/services/test_aws_sdk_task_service.py @@ -30,7 +30,7 @@ def test_list_secrets( ): template = ST.load_sfn_template(ST.AWSSDK_LIST_SECRETS) definition = json.dumps(template) - exec_input = json.dumps(dict()) + exec_input = json.dumps({}) create_and_record_execution( aws_client, create_state_machine_iam_role, diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api.py b/tests/aws/services/stepfunctions/v2/test_sfn_api.py index b46ec48bd3361..96f7300258a35 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api.py @@ -316,7 +316,7 @@ def test_list_sms( f"statemachine_2_{short_uid()}", f"statemachine_3_{short_uid()}", ] - state_machine_arns = list() + state_machine_arns = [] for i, sm_name in enumerate(sm_names): creation_resp = create_state_machine( @@ -371,7 +371,7 @@ def test_list_sms_pagination( definition_str = json.dumps(definition) sm_names = [f"statemachine_{i}_{short_uid()}" for i in range(13)] - state_machine_arns = list() + state_machine_arns = [] for i, sm_name in enumerate(sm_names): creation_resp = create_state_machine( @@ -646,9 +646,9 @@ def test_list_executions_pagination( state_machine_arn=state_machine_arn, ) - execution_arns = list() + execution_arns = [] for i in range(13): - input_data = json.dumps(dict()) + input_data = json.dumps({}) exec_resp = aws_client.stepfunctions.start_execution( stateMachineArn=state_machine_arn, input=input_data @@ -759,9 +759,9 @@ def test_list_executions_versions_pagination( state_machine_version_arn=state_machine_version_arn, ) - execution_arns = list() + execution_arns = [] for i in range(13): - input_data = json.dumps(dict()) + input_data = json.dumps({}) exec_resp = aws_client.stepfunctions.start_execution( stateMachineArn=state_machine_version_arn, input=input_data diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py index b1c1b100a9316..20ddd61043ce4 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_aliasing.py @@ -303,7 +303,7 @@ def test_error_create_alias_invalid_router_configs( ) state_machine_arn = create_state_machine_response["stateMachineArn"] - state_machine_version_arns: list[Arn] = list() + state_machine_version_arns: list[Arn] = [] state_machine_version_arns.append(create_state_machine_response["stateMachineVersionArn"]) for version_number in range(2): definition["Comment"] = f"Definition for version {version_number}" @@ -531,7 +531,7 @@ def test_base_lifecycle_create_delete_list( sfn_snapshot.add_transformer( RegexTransformer(state_machine_alias_base_name, "state_machine_alias_base_name") ) - state_machine_alias_arns: list[str] = list() + state_machine_alias_arns: list[str] = [] for num in range(3): state_machine_alias_name = f"{state_machine_alias_base_name}-{num}" create_state_machine_alias_response = create_state_machine_alias( @@ -788,7 +788,7 @@ def test_base_lifecycle_create_update_describe( ) state_machine_arn = create_state_machine_response["stateMachineArn"] - state_machine_version_arns: list[Arn] = list() + state_machine_version_arns: list[Arn] = [] state_machine_version_arns.append(create_state_machine_response["stateMachineVersionArn"]) for version_number in range(2): definition["Comment"] = f"Definition for version {version_number}" diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py index c193ee4432cb7..fc594475787d9 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_express.py @@ -77,7 +77,7 @@ def test_start_async_describe_history_execution( ): definition = ServicesTemplates.load_sfn_template(BaseTemplate.BASE_PASS_RESULT) definition_str = json.dumps(definition) - execution_input = json.dumps(dict()) + execution_input = json.dumps({}) state_machine_arn, execution_arn = create_and_record_express_async_execution( aws_client, create_state_machine_iam_role, diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py index 75d6af6532d87..f349be85afd5d 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_logs.py @@ -33,7 +33,7 @@ ] _TEST_INCOMPLETE_LOGGING_CONFIGURATIONS = [ LoggingConfiguration(), - LoggingConfiguration(destinations=list()), + LoggingConfiguration(destinations=[]), ] diff --git a/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py b/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py index 2a9e7dd020e8e..1ffc74f4f3c84 100644 --- a/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py +++ b/tests/aws/services/stepfunctions/v2/test_sfn_api_versioning.py @@ -192,7 +192,7 @@ def test_list_state_machine_versions_pagination( sfn_snapshot.match("creation_resp_1", creation_resp_1) state_machine_arn = creation_resp_1["stateMachineArn"] - state_machine_version_arns = list() + state_machine_version_arns = [] for revision_no in range(1, 14): definition["Comment"] = f"{definition['Comment']}-R{revision_no}" definition_raw_str = json.dumps(definition) diff --git a/tests/aws/services/sts/test_sts.py b/tests/aws/services/sts/test_sts.py index 6488f4f13799c..e5163627ce914 100644 --- a/tests/aws/services/sts/test_sts.py +++ b/tests/aws/services/sts/test_sts.py @@ -188,12 +188,8 @@ def test_assume_role_with_saml(self, aws_client): fed_name=fed_name, ).replace("\n", "") - role_arn = "arn:aws:iam::{account_id}:role/{role_name}".format( - account_id=account_id, role_name=role_name - ) - principal_arn = "arn:aws:iam:{account_id}:saml-provider/{provider_name}".format( - account_id=account_id, provider_name=provider_name - ) + role_arn = f"arn:aws:iam::{account_id}:role/{role_name}" + principal_arn = f"arn:aws:iam:{account_id}:saml-provider/{provider_name}" base64_saml_assertion = b64encode(saml_assertion.encode("utf-8")).decode("utf-8") response = aws_client.sts.assume_role_with_saml( RoleArn=role_arn, diff --git a/tests/aws/services/swf/test_swf.py b/tests/aws/services/swf/test_swf.py index ba42346656a4e..3871fccd14b66 100644 --- a/tests/aws/services/swf/test_swf.py +++ b/tests/aws/services/swf/test_swf.py @@ -15,9 +15,9 @@ def test_run_workflow(self, aws_client): swf_client = aws_client.swf swf_unique_id = short_uid() - workflow_domain_name = "test-swf-domain-{}".format(swf_unique_id) - workflow_type_name = "test-swf-workflow-{}".format(swf_unique_id) - workflow_activity_name = "test-swf-activity-{}".format(swf_unique_id) + workflow_domain_name = f"test-swf-domain-{swf_unique_id}" + workflow_type_name = f"test-swf-workflow-{swf_unique_id}" + workflow_activity_name = f"test-swf-activity-{swf_unique_id}" swf_client.register_domain( name=workflow_domain_name, workflowExecutionRetentionPeriodInDays="1" diff --git a/tests/aws/templates/cdk_bootstrap_v28.yaml b/tests/aws/templates/cdk_bootstrap_v28.yaml new file mode 100644 index 0000000000000..7083afbc023f5 --- /dev/null +++ b/tests/aws/templates/cdk_bootstrap_v28.yaml @@ -0,0 +1,671 @@ +Description: This stack includes resources needed to deploy AWS CDK apps into this environment +Parameters: + TrustedAccounts: + Description: List of AWS accounts that are trusted to publish assets and deploy stacks to this environment + Default: "" + Type: CommaDelimitedList + TrustedAccountsForLookup: + Description: List of AWS accounts that are trusted to look up values in this environment + Default: "" + Type: CommaDelimitedList + CloudFormationExecutionPolicies: + Description: List of the ManagedPolicy ARN(s) to attach to the CloudFormation deployment role + Default: "" + Type: CommaDelimitedList + FileAssetsBucketName: + Description: The name of the S3 bucket used for file assets + Default: "" + Type: String + FileAssetsBucketKmsKeyId: + Description: Empty to create a new key (default), 'AWS_MANAGED_KEY' to use a managed S3 key, or the ID/ARN of an existing key. + Default: "" + Type: String + ContainerAssetsRepositoryName: + Description: A user-provided custom name to use for the container assets ECR repository + Default: "" + Type: String + Qualifier: + Description: An identifier to distinguish multiple bootstrap stacks in the same environment + Default: hnb659fds + Type: String + AllowedPattern: "[A-Za-z0-9_-]{1,10}" + ConstraintDescription: Qualifier must be an alphanumeric identifier of at most 10 characters + PublicAccessBlockConfiguration: + Description: Whether or not to enable S3 Staging Bucket Public Access Block Configuration + Default: "true" + Type: String + AllowedValues: + - "true" + - "false" + InputPermissionsBoundary: + Description: Whether or not to use either the CDK supplied or custom permissions boundary + Default: "" + Type: String + UseExamplePermissionsBoundary: + Default: "false" + AllowedValues: + - "true" + - "false" + Type: String + BootstrapVariant: + Type: String + Default: "AWS CDK: Default Resources" + Description: Describe the provenance of the resources in this bootstrap stack. Change this when you customize the template. To prevent accidents, the CDK CLI will not overwrite bootstrap stacks with a different variant. +Conditions: + HasTrustedAccounts: + Fn::Not: + - Fn::Equals: + - "" + - Fn::Join: + - "" + - Ref: TrustedAccounts + HasTrustedAccountsForLookup: + Fn::Not: + - Fn::Equals: + - "" + - Fn::Join: + - "" + - Ref: TrustedAccountsForLookup + HasCloudFormationExecutionPolicies: + Fn::Not: + - Fn::Equals: + - "" + - Fn::Join: + - "" + - Ref: CloudFormationExecutionPolicies + HasCustomFileAssetsBucketName: + Fn::Not: + - Fn::Equals: + - "" + - Ref: FileAssetsBucketName + CreateNewKey: + Fn::Equals: + - "" + - Ref: FileAssetsBucketKmsKeyId + UseAwsManagedKey: + Fn::Equals: + - AWS_MANAGED_KEY + - Ref: FileAssetsBucketKmsKeyId + ShouldCreatePermissionsBoundary: + Fn::Equals: + - "true" + - Ref: UseExamplePermissionsBoundary + PermissionsBoundarySet: + Fn::Not: + - Fn::Equals: + - "" + - Ref: InputPermissionsBoundary + HasCustomContainerAssetsRepositoryName: + Fn::Not: + - Fn::Equals: + - "" + - Ref: ContainerAssetsRepositoryName + UsePublicAccessBlockConfiguration: + Fn::Equals: + - "true" + - Ref: PublicAccessBlockConfiguration +Resources: + FileAssetsBucketEncryptionKey: + Type: AWS::KMS::Key + Properties: + KeyPolicy: + Statement: + - Action: + - kms:Create* + - kms:Describe* + - kms:Enable* + - kms:List* + - kms:Put* + - kms:Update* + - kms:Revoke* + - kms:Disable* + - kms:Get* + - kms:Delete* + - kms:ScheduleKeyDeletion + - kms:CancelKeyDeletion + - kms:GenerateDataKey + - kms:TagResource + - kms:UntagResource + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + Resource: "*" + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + AWS: "*" + Resource: "*" + Condition: + StringEquals: + kms:CallerAccount: + Ref: AWS::AccountId + kms:ViaService: + - Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Principal: + AWS: + Fn::Sub: ${FilePublishingRole.Arn} + Resource: "*" + Condition: CreateNewKey + UpdateReplacePolicy: Delete + DeletionPolicy: Delete + FileAssetsBucketEncryptionKeyAlias: + Condition: CreateNewKey + Type: AWS::KMS::Alias + Properties: + AliasName: + Fn::Sub: alias/cdk-${Qualifier}-assets-key + TargetKeyId: + Ref: FileAssetsBucketEncryptionKey + StagingBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: + Fn::If: + - HasCustomFileAssetsBucketName + - Fn::Sub: ${FileAssetsBucketName} + - Fn::Sub: cdk-${Qualifier}-assets-${AWS::AccountId}-${AWS::Region} + AccessControl: Private + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: aws:kms + KMSMasterKeyID: + Fn::If: + - CreateNewKey + - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} + - Fn::If: + - UseAwsManagedKey + - Ref: AWS::NoValue + - Fn::Sub: ${FileAssetsBucketKmsKeyId} + PublicAccessBlockConfiguration: + Fn::If: + - UsePublicAccessBlockConfiguration + - BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + - Ref: AWS::NoValue + VersioningConfiguration: + Status: Enabled + LifecycleConfiguration: + Rules: + - Id: CleanupOldVersions + Status: Enabled + NoncurrentVersionExpiration: + NoncurrentDays: 30 + - Id: AbortIncompleteMultipartUploads + Status: Enabled + AbortIncompleteMultipartUpload: + DaysAfterInitiation: 1 + UpdateReplacePolicy: Retain + DeletionPolicy: Retain + StagingBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Ref: StagingBucket + PolicyDocument: + Id: AccessControl + Version: "2012-10-17" + Statement: + - Sid: AllowSSLRequestsOnly + Action: s3:* + Effect: Deny + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + Condition: + Bool: + aws:SecureTransport: "false" + Principal: "*" + ContainerAssetsRepository: + Type: AWS::ECR::Repository + Properties: + ImageTagMutability: IMMUTABLE + LifecyclePolicy: + LifecyclePolicyText: | + { + "rules": [ + { + "rulePriority": 1, + "description": "Untagged images should not exist, but expire any older than one year", + "selection": { + "tagStatus": "untagged", + "countType": "sinceImagePushed", + "countUnit": "days", + "countNumber": 365 + }, + "action": { "type": "expire" } + } + ] + } + RepositoryName: + Fn::If: + - HasCustomContainerAssetsRepositoryName + - Fn::Sub: ${ContainerAssetsRepositoryName} + - Fn::Sub: cdk-${Qualifier}-container-assets-${AWS::AccountId}-${AWS::Region} + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + - Sid: LambdaECRImageRetrievalPolicy + Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Condition: + StringLike: + aws:sourceArn: + Fn::Sub: arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:* + - Sid: EmrServerlessImageRetrievalPolicy + Effect: Allow + Principal: + Service: emr-serverless.amazonaws.com + Action: + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + - ecr:DescribeImages + Condition: + StringLike: + aws:sourceArn: + Fn::Sub: arn:${AWS::Partition}:emr-serverless:${AWS::Region}:${AWS::AccountId}:/applications/* + FilePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: file-publishing + ImagePublishingRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: image-publishing + LookupRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccountsForLookup + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccountsForLookup + - Ref: AWS::NoValue + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + RoleName: + Fn::Sub: cdk-${Qualifier}-lookup-role-${AWS::AccountId}-${AWS::Region} + ManagedPolicyArns: + - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/ReadOnlyAccess + Policies: + - PolicyDocument: + Statement: + - Sid: DontReadSecrets + Effect: Deny + Action: + - kms:Decrypt + Resource: "*" + Version: "2012-10-17" + PolicyName: LookupRolePolicy + Tags: + - Key: aws-cdk:bootstrap-role + Value: lookup + FilePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - s3:GetObject* + - s3:GetBucket* + - s3:GetEncryptionConfiguration + - s3:List* + - s3:DeleteObject* + - s3:PutObject* + - s3:Abort* + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + Condition: + StringEquals: + aws:ResourceAccount: + - Fn::Sub: ${AWS::AccountId} + Effect: Allow + - Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Effect: Allow + Resource: + Fn::If: + - CreateNewKey + - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} + - Fn::Sub: arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/${FileAssetsBucketKmsKeyId} + Version: "2012-10-17" + Roles: + - Ref: FilePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-file-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + ImagePublishingRoleDefaultPolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - ecr:PutImage + - ecr:InitiateLayerUpload + - ecr:UploadLayerPart + - ecr:CompleteLayerUpload + - ecr:BatchCheckLayerAvailability + - ecr:DescribeRepositories + - ecr:DescribeImages + - ecr:BatchGetImage + - ecr:GetDownloadUrlForLayer + Resource: + Fn::Sub: ${ContainerAssetsRepository.Arn} + Effect: Allow + - Action: + - ecr:GetAuthorizationToken + Resource: "*" + Effect: Allow + Version: "2012-10-17" + Roles: + - Ref: ImagePublishingRole + PolicyName: + Fn::Sub: cdk-${Qualifier}-image-publishing-role-default-policy-${AWS::AccountId}-${AWS::Region} + DeploymentActionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:TagSession + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: AWS::AccountId + - Fn::If: + - HasTrustedAccounts + - Action: sts:AssumeRole + Effect: Allow + Principal: + AWS: + Ref: TrustedAccounts + - Ref: AWS::NoValue + Policies: + - PolicyDocument: + Statement: + - Sid: CloudFormationPermissions + Effect: Allow + Action: + - cloudformation:CreateChangeSet + - cloudformation:DeleteChangeSet + - cloudformation:DescribeChangeSet + - cloudformation:DescribeStacks + - cloudformation:ExecuteChangeSet + - cloudformation:CreateStack + - cloudformation:UpdateStack + - cloudformation:RollbackStack + - cloudformation:ContinueUpdateRollback + Resource: "*" + - Sid: PipelineCrossAccountArtifactsBucket + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + - s3:Abort* + - s3:DeleteObject* + - s3:PutObject* + Resource: "*" + Condition: + StringNotEquals: + s3:ResourceAccount: + Ref: AWS::AccountId + - Sid: PipelineCrossAccountArtifactsKey + Effect: Allow + Action: + - kms:Decrypt + - kms:DescribeKey + - kms:Encrypt + - kms:ReEncrypt* + - kms:GenerateDataKey* + Resource: "*" + Condition: + StringEquals: + kms:ViaService: + Fn::Sub: s3.${AWS::Region}.amazonaws.com + - Action: iam:PassRole + Resource: + Fn::Sub: ${CloudFormationExecutionRole.Arn} + Effect: Allow + - Sid: CliPermissions + Action: + - cloudformation:DescribeStackEvents + - cloudformation:GetTemplate + - cloudformation:DeleteStack + - cloudformation:UpdateTerminationProtection + - sts:GetCallerIdentity + - cloudformation:GetTemplateSummary + Resource: "*" + Effect: Allow + - Sid: CliStagingBucket + Effect: Allow + Action: + - s3:GetObject* + - s3:GetBucket* + - s3:List* + Resource: + - Fn::Sub: ${StagingBucket.Arn} + - Fn::Sub: ${StagingBucket.Arn}/* + - Sid: ReadVersion + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + Resource: + - Fn::Sub: arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter${CdkBootstrapVersion} + - Sid: Refactor + Effect: Allow + Action: + - cloudformation:CreateStackRefactor + - cloudformation:DescribeStackRefactor + - cloudformation:ExecuteStackRefactor + - cloudformation:ListStackRefactorActions + - cloudformation:ListStackRefactors + - cloudformation:ListStacks + Resource: "*" + Version: "2012-10-17" + PolicyName: default + RoleName: + Fn::Sub: cdk-${Qualifier}-deploy-role-${AWS::AccountId}-${AWS::Region} + Tags: + - Key: aws-cdk:bootstrap-role + Value: deploy + CloudFormationExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: cloudformation.amazonaws.com + Version: "2012-10-17" + ManagedPolicyArns: + Fn::If: + - HasCloudFormationExecutionPolicies + - Ref: CloudFormationExecutionPolicies + - Fn::If: + - HasTrustedAccounts + - Ref: AWS::NoValue + - - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess + RoleName: + Fn::Sub: cdk-${Qualifier}-cfn-exec-role-${AWS::AccountId}-${AWS::Region} + PermissionsBoundary: + Fn::If: + - PermissionsBoundarySet + - Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/${InputPermissionsBoundary} + - Ref: AWS::NoValue + CdkBoostrapPermissionsBoundaryPolicy: + Condition: ShouldCreatePermissionsBoundary + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Statement: + - Sid: ExplicitAllowAll + Action: + - "*" + Effect: Allow + Resource: "*" + - Sid: DenyAccessIfRequiredPermBoundaryIsNotBeingApplied + Action: + - iam:CreateUser + - iam:CreateRole + - iam:PutRolePermissionsBoundary + - iam:PutUserPermissionsBoundary + Condition: + StringNotEquals: + iam:PermissionsBoundary: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Effect: Deny + Resource: "*" + - Sid: DenyPermBoundaryIAMPolicyAlteration + Action: + - iam:CreatePolicyVersion + - iam:DeletePolicy + - iam:DeletePolicyVersion + - iam:SetDefaultPolicyVersion + Effect: Deny + Resource: + Fn::Sub: arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + - Sid: DenyRemovalOfPermBoundaryFromAnyUserOrRole + Action: + - iam:DeleteUserPermissionsBoundary + - iam:DeleteRolePermissionsBoundary + Effect: Deny + Resource: "*" + Version: "2012-10-17" + Description: Bootstrap Permission Boundary + ManagedPolicyName: + Fn::Sub: cdk-${Qualifier}-permissions-boundary-${AWS::AccountId}-${AWS::Region} + Path: / + CdkBootstrapVersion: + Type: AWS::SSM::Parameter + Properties: + Type: String + Name: + Fn::Sub: /cdk-bootstrap/${Qualifier}/version + Value: "28" +Outputs: + BucketName: + Description: The name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: ${StagingBucket} + BucketDomainName: + Description: The domain name of the S3 bucket owned by the CDK toolkit stack + Value: + Fn::Sub: ${StagingBucket.RegionalDomainName} + FileAssetKeyArn: + Description: The ARN of the KMS key used to encrypt the asset bucket (deprecated) + Value: + Fn::If: + - CreateNewKey + - Fn::Sub: ${FileAssetsBucketEncryptionKey.Arn} + - Fn::Sub: ${FileAssetsBucketKmsKeyId} + Export: + Name: + Fn::Sub: CdkBootstrap-${Qualifier}-FileAssetKeyArn + ImageRepositoryName: + Description: The name of the ECR repository which hosts docker image assets + Value: + Fn::Sub: ${ContainerAssetsRepository} + BootstrapVersion: + Description: The version of the bootstrap resources that are currently mastered in this stack + Value: + Fn::GetAtt: + - CdkBootstrapVersion + - Value diff --git a/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml b/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml index 21161124d0ed0..c49fa319b5557 100644 --- a/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml +++ b/tests/aws/templates/cfn_lambda_with_external_api_paths_in_env_vars.yaml @@ -1,3 +1,6 @@ +Parameters: + CustomURL: + Type: String Resources: SimpleFnServiceRole6574647D: Type: AWS::IAM::Role @@ -35,6 +38,7 @@ Resources: API_URL_2: https://storage.execute-api.us-east-2.amazonaws.com/test-resource API_URL_3: https://reporting.execute-api.us-east-1.amazonaws.com/test-resource API_URL_4: https://blockchain.execute-api.us-west-1.amazonaws.com/test-resource + API_URL_CUSTOM: !Ref CustomURL DependsOn: - SimpleFnServiceRole6574647D Outputs: diff --git a/tests/aws/templates/engine/fn_select_fn_mapp.yml b/tests/aws/templates/engine/fn_select_fn_mapp.yml new file mode 100644 index 0000000000000..d7bdb845f5c14 --- /dev/null +++ b/tests/aws/templates/engine/fn_select_fn_mapp.yml @@ -0,0 +1,30 @@ +Mappings: + RegionMap: + us-east-1: + AZs: + - "empty" + +Resources: + Parameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: + Fn::If: [UseFnGetAZs, "true", "false"] + Name: commands + + +Conditions: + UseFnGetAZs: + Fn::Equals: + - "empty" + - Fn::Select: + - 0 + - Fn::FindInMap: + - "RegionMap" + - "us-east-1" + - "AZs" + +Outputs: + ParameterValue: + Value: !GetAtt Parameter.Value \ No newline at end of file diff --git a/tests/aws/templates/macros/add_standard_tags.py b/tests/aws/templates/macros/add_standard_tags.py new file mode 100644 index 0000000000000..dffe4ffb631e7 --- /dev/null +++ b/tests/aws/templates/macros/add_standard_tags.py @@ -0,0 +1,10 @@ +def handler(event, context): + fragment = add_standard_attributes(event["fragment"]) + + return {"requestId": event["requestId"], "status": "success", "fragment": fragment} + + +def add_standard_attributes(fragment): + fragment["Tags"] = {"MacroAdded": "True"} + + return fragment diff --git a/tests/aws/test_moto.py b/tests/aws/test_moto.py index e28b4fd71ebc9..d5f4aa48af582 100644 --- a/tests/aws/test_moto.py +++ b/tests/aws/test_moto.py @@ -1,5 +1,4 @@ from io import BytesIO -from typing import Optional import pytest from moto.core import DEFAULT_ACCOUNT_ID as DEFAULT_MOTO_ACCOUNT_ID @@ -306,7 +305,7 @@ class FakeNoSuchBucket(ServiceException): code: str = "NoSuchBucket" sender_fault: bool = False status_code: int = 404 - BucketName: Optional[str] + BucketName: str | None def __init__(self) -> None: super().__init__() diff --git a/tests/bootstrap/conftest.py b/tests/bootstrap/conftest.py index 9886d22853fa9..9219be34abdb6 100644 --- a/tests/bootstrap/conftest.py +++ b/tests/bootstrap/conftest.py @@ -1,5 +1,4 @@ import os -from typing import Optional import pytest @@ -20,7 +19,7 @@ def cdk_template_path(): @pytest.fixture(scope="session") def infrastructure_setup(cdk_template_path, aws_client_factory): def _infrastructure_setup( - namespace: str, force_synth: Optional[bool] = False, port: int = constants.DEFAULT_PORT_EDGE + namespace: str, force_synth: bool | None = False, port: int = constants.DEFAULT_PORT_EDGE ) -> InfraProvisioner: """ :param namespace: repo-unique identifier for this CDK app. diff --git a/tests/bootstrap/test_cosmetic_configuration.py b/tests/bootstrap/test_cosmetic_configuration.py index 37610216c3b25..ebdc7aee17337 100644 --- a/tests/bootstrap/test_cosmetic_configuration.py +++ b/tests/bootstrap/test_cosmetic_configuration.py @@ -1,6 +1,6 @@ import io import os -from typing import Generator, Type +from collections.abc import Generator from urllib.parse import urlparse import aws_cdk as cdk @@ -87,7 +87,7 @@ def container( def raise_exception_with_cloudwatch_logs( - aws_client: ServiceLevelClientFactory, exc_class: Type[Exception] = AssertionError + aws_client: ServiceLevelClientFactory, exc_class: type[Exception] = AssertionError ): out = io.StringIO() diff --git a/tests/conftest.py b/tests/conftest.py index 2a23489c537bc..38f80266897fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,9 @@ import os import pytest +from _pytest.monkeypatch import MonkeyPatch + +from localstack import config os.environ["LOCALSTACK_INTERNAL_TEST_RUN"] = "1" @@ -87,3 +90,16 @@ def secondary_aws_client(secondary_aws_client_factory): from localstack.testing.aws.util import base_testing_aws_client return base_testing_aws_client(secondary_aws_client_factory) + + +@pytest.fixture(scope="session", autouse=True) +def enable_stack_trace_for_tests(): + """ + Ensure stack traces are enabled in HTTP responses during test sessions. + + This is useful for debugging purposes. + """ + mpatch = MonkeyPatch() + mpatch.setattr(config, "INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE", True) + yield mpatch + mpatch.undo() diff --git a/tests/integration/docker_utils/conftest.py b/tests/integration/docker_utils/conftest.py index 39860824f8a58..a247666b56214 100644 --- a/tests/integration/docker_utils/conftest.py +++ b/tests/integration/docker_utils/conftest.py @@ -1,5 +1,4 @@ import os -from typing import Type import pytest @@ -22,7 +21,7 @@ def _check_skip(client: ContainerClient): ids=["CmdDockerClient", "SdkDockerClient"], scope="class", ) -def docker_client_class(request) -> Type[ContainerClient]: +def docker_client_class(request) -> type[ContainerClient]: return request.param diff --git a/tests/integration/docker_utils/test_docker.py b/tests/integration/docker_utils/test_docker.py index 4f62f0076bfce..4ebaf973e7e3f 100644 --- a/tests/integration/docker_utils/test_docker.py +++ b/tests/integration/docker_utils/test_docker.py @@ -6,7 +6,8 @@ import re import textwrap import time -from typing import Callable, NamedTuple, Type +from collections.abc import Callable +from typing import NamedTuple import pytest from docker.models.containers import Container @@ -48,13 +49,11 @@ from localstack.utils.threads import FuncThread from tests.integration.docker_utils.conftest import is_podman_test, skip_for_podman -ContainerInfo = NamedTuple( - "ContainerInfo", - [ - ("container_id", str), - ("container_name", str), - ], -) + +class ContainerInfo(NamedTuple): + container_id: str + container_name: str + LOG = logging.getLogger(__name__) @@ -365,7 +364,7 @@ def test_start_non_existing_container(self, docker_client: ContainerClient): with pytest.raises(NoSuchContainer): docker_client.start_container("this_container_does_not_exist") - def test_docker_not_available(self, docker_client_class: Type[ContainerClient], monkeypatch): + def test_docker_not_available(self, docker_client_class: type[ContainerClient], monkeypatch): monkeypatch.setattr(config, "DOCKER_CMD", "non-existing-binary") monkeypatch.setenv("DOCKER_HOST", "/var/run/docker.sock1") # initialize the client after mocking the environment diff --git a/tests/integration/services/test_internal.py b/tests/integration/services/test_internal.py index c32d4d8676b3e..00f65aa429a76 100644 --- a/tests/integration/services/test_internal.py +++ b/tests/integration/services/test_internal.py @@ -60,4 +60,4 @@ def test_get(self): assert doc["session_id"] assert doc["machine_id"] assert doc["system"] - assert type(doc["is_license_activated"]) == bool + assert isinstance(doc["is_license_activated"], bool) diff --git a/tests/integration/test_config_service.py b/tests/integration/test_config_service.py index 03dad8d204de5..35792ac52eda9 100644 --- a/tests/integration/test_config_service.py +++ b/tests/integration/test_config_service.py @@ -33,7 +33,7 @@ def _create_config_recorder(iam_role_arn: str): def test_put_configuration_recorder( self, aws_client, create_role, create_configuration_recorder ): - iam_role_name = "role-{}".format(short_uid()) + iam_role_name = f"role-{short_uid()}" iam_role_arn = create_role( RoleName=iam_role_name, AssumeRolePolicyDocument=json.dumps(ASSUME_POLICY_DOCUMENT) )["Role"]["Arn"] @@ -57,7 +57,7 @@ def test_put_configuration_recorder( def test_put_delivery_channel( self, aws_client, s3_create_bucket, create_role, create_configuration_recorder ): - iam_role_name = "role-{}".format(short_uid()) + iam_role_name = f"role-{short_uid()}" iam_role_arn = create_role( RoleName=iam_role_name, AssumeRolePolicyDocument=json.dumps(ASSUME_POLICY_DOCUMENT) )["Role"]["Arn"] diff --git a/tests/unit/aws/protocol/test_parser.py b/tests/unit/aws/protocol/test_parser.py index f647daed9d4be..a7b5f8cb61fd0 100644 --- a/tests/unit/aws/protocol/test_parser.py +++ b/tests/unit/aws/protocol/test_parser.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import UTC, datetime from io import BytesIO from urllib.parse import unquote, urlencode, urlsplit @@ -450,8 +450,8 @@ def test_query_parser_no_input_shape_autoscaling_with_botocore(): def test_query_parser_iot_with_botocore(): """Test if timestamp for 'rest-json' is parsed correctly""" - start = datetime(2023, 1, 10, tzinfo=timezone.utc) - end = datetime(2023, 1, 11, tzinfo=timezone.utc) + start = datetime(2023, 1, 10, tzinfo=UTC) + end = datetime(2023, 1, 11, tzinfo=UTC) _botocore_parser_integration_test( service="iot", action="ListAuditMitigationActionsTasks", @@ -1229,7 +1229,7 @@ def test_restxml_header_date_parsing(): ContentLength=3, Body=BytesIO(b"foo"), Metadata={}, - Expires=datetime(2015, 1, 1, 0, 0, tzinfo=timezone.utc), + Expires=datetime(2015, 1, 1, 0, 0, tzinfo=UTC), ) diff --git a/tests/unit/aws/protocol/test_serializer.py b/tests/unit/aws/protocol/test_serializer.py index 156abc589644e..8d8dbb5b1dd57 100644 --- a/tests/unit/aws/protocol/test_serializer.py +++ b/tests/unit/aws/protocol/test_serializer.py @@ -2,9 +2,10 @@ import io import json import re +from collections.abc import Iterator from datetime import datetime from io import BytesIO -from typing import Any, Dict, Iterator, List, Optional +from typing import Any from xml.etree import ElementTree import pytest @@ -119,9 +120,9 @@ def _botocore_error_serializer_integration_test( exception: ServiceException, code: str, status_code: int, - message: Optional[str], + message: str | None, is_sender_fault: bool = False, - **additional_error_fields: Dict[str, Any], + **additional_error_fields: dict[str, Any], ) -> dict: """ Performs an integration test for the error serialization using botocore as parser. @@ -195,7 +196,7 @@ def _botocore_error_serializer_integration_test( def _botocore_event_streaming_test( - service: str, action: str, response: dict, response_root_tag: str, expected_events: List[dict] + service: str, action: str, response: dict, response_root_tag: str, expected_events: list[dict] ): """ Tests the serialization of event streaming responses using botocore. @@ -627,8 +628,8 @@ class NoSuchKey(ServiceException): code: str = "NoSuchKey" sender_fault: bool = False status_code: int = 404 - DeleteMarker: Optional[bool] - VersionId: Optional[str] + DeleteMarker: bool | None + VersionId: str | None exception = NoSuchKey( "You shall not access this API! Sincerely, your friendly neighbourhood firefighter.", @@ -1677,7 +1678,7 @@ def __eq__(self, other): class ComparableBytesIterator(Iterator[bytes]): - def __init__(self, bytes_list: List[bytes]): + def __init__(self, bytes_list: list[bytes]): self.gen = iter(bytes_list) self.value = b"".join(bytes_list) @@ -1788,8 +1789,8 @@ def test_restjson_streaming_payload(payload): ) def test_accept_header_detection( service: str, - accept_header: Optional[str], - content_type_header: Optional[str], + accept_header: str | None, + content_type_header: str | None, expected_mime_type: str, ): service_model = load_service(service) diff --git a/tests/unit/aws/test_service_router.py b/tests/unit/aws/test_service_router.py index 00768ff4844a1..8a7c6f8722892 100644 --- a/tests/unit/aws/test_service_router.py +++ b/tests/unit/aws/test_service_router.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, Tuple +from typing import Any from urllib.parse import urlsplit import pytest @@ -13,7 +13,7 @@ from localstack.utils.run import to_str -def _collect_operations() -> Tuple[ServiceModel, OperationModel]: +def _collect_operations() -> tuple[ServiceModel, OperationModel]: """ Collects all service<>operation combinations to test. """ @@ -132,7 +132,7 @@ def _botocore_request_to_localstack_request(request_object: AWSRequest) -> Reque } -def _create_dummy_request_args(operation_model: OperationModel) -> Dict: +def _create_dummy_request_args(operation_model: OperationModel) -> dict: """Creates a dummy request param dict for the given operation.""" input_shape: StructureShape = operation_model.input_shape if not input_shape: diff --git a/tests/unit/aws/test_skeleton.py b/tests/unit/aws/test_skeleton.py index 846d41340cd18..d61099b839868 100644 --- a/tests/unit/aws/test_skeleton.py +++ b/tests/unit/aws/test_skeleton.py @@ -1,4 +1,4 @@ -from typing import Dict, List, TypedDict +from typing import TypedDict import pytest from botocore.parsers import create_parser @@ -18,10 +18,10 @@ """ Stripped down version of the SQS API generated by the Scaffold. """ String = str -StringList = List[String] +StringList = list[String] Binary = bytes -BinaryList = List[Binary] +BinaryList = list[Binary] Integer = int @@ -64,9 +64,9 @@ class MessageSystemAttributeValue(TypedDict): DataType: String -MessageBodyAttributeMap = Dict[String, MessageAttributeValue] -MessageSystemAttributeMap = Dict[MessageSystemAttributeName, String] -MessageBodySystemAttributeMap = Dict[ +MessageBodyAttributeMap = dict[String, MessageAttributeValue] +MessageSystemAttributeMap = dict[MessageSystemAttributeName, String] +MessageBodySystemAttributeMap = dict[ MessageSystemAttributeNameForSends, MessageSystemAttributeValue ] @@ -330,7 +330,7 @@ class SomeAction(ServiceRequest): ArgTwo: int def fn(context, arg_one, arg_two): - assert type(context) == RequestContext + assert isinstance(context, RequestContext) assert arg_one == "foo" assert arg_two == 69 @@ -340,7 +340,7 @@ def fn(context, arg_one, arg_two): def test_without_context_without_expand(self): def fn(*args): assert len(args) == 1 - assert type(args[0]) == dict + assert isinstance(args[0], dict) dispatcher = ServiceRequestDispatcher( fn, "SomeAction", pass_context=False, expand_parameters=False @@ -350,8 +350,8 @@ def fn(*args): def test_without_expand(self): def fn(*args): assert len(args) == 2 - assert type(args[0]) == RequestContext - assert type(args[1]) == dict + assert isinstance(args[0], RequestContext) + assert isinstance(args[1], dict) dispatcher = ServiceRequestDispatcher( fn, "SomeAction", pass_context=True, expand_parameters=False @@ -360,7 +360,7 @@ def fn(*args): def test_dispatch_without_args(self): def fn(context): - assert type(context) == RequestContext + assert isinstance(context, RequestContext) dispatcher = ServiceRequestDispatcher(fn, "SomeAction") dispatcher(RequestContext(None), ServiceRequest()) diff --git a/tests/unit/aws/test_spec.py b/tests/unit/aws/test_spec.py index b20b57ad76087..d99f6ffbb5724 100644 --- a/tests/unit/aws/test_spec.py +++ b/tests/unit/aws/test_spec.py @@ -1,5 +1,3 @@ -from typing import Type - import pytest from botocore.exceptions import UnknownServiceError from botocore.model import ServiceModel, StringShape @@ -121,7 +119,7 @@ def test_protocol_specific_loading( ], ) def test_invalid_service_loading( - service_name: ServiceName, protocol: ProtocolName, expected_exception: Type[Exception] + service_name: ServiceName, protocol: ProtocolName, expected_exception: type[Exception] ): with pytest.raises(expected_exception): load_service(service=service_name, protocol=protocol) diff --git a/tests/unit/cli/test_lpm.py b/tests/unit/cli/test_lpm.py index 605aac7ef00ad..4bf7c75564dcb 100644 --- a/tests/unit/cli/test_lpm.py +++ b/tests/unit/cli/test_lpm.py @@ -1,5 +1,4 @@ import os.path -from typing import List import pytest from click.testing import CliRunner @@ -48,7 +47,7 @@ class FailingPackage(Package): def __init__(self): super().__init__("Failing Installer", "latest") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["latest"] def _get_installer(self, version: str) -> PackageInstaller: @@ -69,7 +68,7 @@ class SuccessfulPackage(Package): def __init__(self): super().__init__("Successful Installer", "latest") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["latest"] def _get_installer(self, version: str) -> PackageInstaller: @@ -86,7 +85,7 @@ def _get_install_marker_path(self, install_dir: str) -> str: def _install(self, target: InstallTarget) -> None: pass - def patched_get_packages(*_) -> List[Package]: + def patched_get_packages(*_) -> list[Package]: return [FailingPackage(), SuccessfulPackage()] with Patch.function(target=PackagesPluginManager.get_packages, fn=patched_get_packages): diff --git a/tests/unit/http_/test_asgi.py b/tests/unit/http_/test_asgi.py index dbed33d8be862..663508e4e1f92 100644 --- a/tests/unit/http_/test_asgi.py +++ b/tests/unit/http_/test_asgi.py @@ -5,7 +5,6 @@ from concurrent.futures import ThreadPoolExecutor from queue import Queue from threading import Thread -from typing import List import pytest import requests @@ -17,7 +16,7 @@ def test_serve_asgi_adapter(serve_asgi_adapter): - request_list: List[Request] = [] + request_list: list[Request] = [] @Request.application def app(request: Request) -> Response: @@ -136,7 +135,7 @@ def _gen(): def test_chunked_transfer_encoding_request(serve_asgi_adapter): - request_list: List[Request] = [] + request_list: list[Request] = [] @Request.application def app(request: Request) -> Response: diff --git a/tests/unit/http_/test_dispatcher.py b/tests/unit/http_/test_dispatcher.py index 1a4b2421b5e26..41af4071410f4 100644 --- a/tests/unit/http_/test_dispatcher.py +++ b/tests/unit/http_/test_dispatcher.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any import pytest from werkzeug.exceptions import NotFound @@ -14,7 +14,7 @@ def test_handler_dispatcher(self): def handler_foo(_request: Request) -> Response: return Response("ok") - def handler_bar(_request: Request, bar, baz) -> Dict[str, any]: + def handler_bar(_request: Request, bar, baz) -> dict[str, any]: response = Response() response.set_json({"bar": bar, "baz": baz}) return response @@ -42,7 +42,7 @@ def handler(_request: Request, arg1) -> Response: # invalid signature def test_handler_dispatcher_with_dict_return(self): router = Router(dispatcher=handler_dispatcher()) - def handler(_request: Request, arg1) -> Dict[str, Any]: + def handler(_request: Request, arg1) -> dict[str, Any]: return {"arg1": arg1, "hello": "there"} router.add("/foo/", handler) diff --git a/tests/unit/http_/test_hypercorn.py b/tests/unit/http_/test_hypercorn.py index 85693337c9228..5eee76bf5888d 100644 --- a/tests/unit/http_/test_hypercorn.py +++ b/tests/unit/http_/test_hypercorn.py @@ -1,6 +1,5 @@ import re from contextlib import contextmanager -from typing import Optional import requests from werkzeug.datastructures import Headers @@ -17,7 +16,7 @@ @contextmanager -def server_context(server: Server, timeout: Optional[float] = 10): +def server_context(server: Server, timeout: float | None = 10): server.start() server.wait_is_up(timeout) try: diff --git a/tests/unit/http_/test_proxy.py b/tests/unit/http_/test_proxy.py index e7b6318bbbd85..28faeb49c96fa 100644 --- a/tests/unit/http_/test_proxy.py +++ b/tests/unit/http_/test_proxy.py @@ -1,5 +1,4 @@ import json -from typing import Tuple import pytest import requests @@ -14,7 +13,7 @@ @pytest.fixture -def router_server(serve_asgi_adapter) -> Tuple[Router, HypercornServer]: +def router_server(serve_asgi_adapter) -> tuple[Router, HypercornServer]: """Creates a new Router with a handler dispatcher, serves it through a newly created ASGI server, and returns both the router and the server. """ diff --git a/tests/unit/http_/test_router.py b/tests/unit/http_/test_router.py index 149cdba1ff005..3d03a1add6772 100644 --- a/tests/unit/http_/test_router.py +++ b/tests/unit/http_/test_router.py @@ -1,5 +1,4 @@ import threading -from typing import List, Tuple import pytest import requests @@ -34,7 +33,7 @@ def echo_params_json(request: Request, params: dict[str, str]): class RequestCollector: """Test dispatcher that collects requests into a list""" - requests: List[Tuple[Request, E, RequestArguments]] + requests: list[tuple[Request, E, RequestArguments]] def __init__(self) -> None: super().__init__() diff --git a/tests/unit/packages/test_api.py b/tests/unit/packages/test_api.py index 608970f25d7c1..789ee2e7d3bca 100644 --- a/tests/unit/packages/test_api.py +++ b/tests/unit/packages/test_api.py @@ -2,7 +2,7 @@ from pathlib import Path from queue import Queue from threading import Event, RLock -from typing import List, Optional +from typing import Optional import pytest @@ -15,7 +15,7 @@ class TestPackage(Package): def __init__(self): super().__init__("Test Package", "test-version") - def get_versions(self) -> List[str]: + def get_versions(self) -> list[str]: return ["test-version"] def _get_installer(self, version: str) -> PackageInstaller: @@ -23,7 +23,7 @@ def _get_installer(self, version: str) -> PackageInstaller: class TestPackageInstaller(PackageInstaller): - def __init__(self, version: str, install_lock: Optional[RLock] = None): + def __init__(self, version: str, install_lock: Optional[RLock] = None): # noqa UP045 super().__init__("test-installer", version, install_lock) def _get_install_marker_path(self, install_dir: str) -> str: @@ -65,7 +65,7 @@ class LockingTestPackageInstaller(PackageInstaller): Package installer class used for testing the locking behavior. """ - def __init__(self, queue: Queue = None, install_lock: Optional[RLock] = None): + def __init__(self, queue: Queue = None, install_lock: Optional[RLock] = None): # noqa UP045 super().__init__("lock-test-installer", "test", install_lock) self.queue = queue or Queue() self.about_to_wait = Event() diff --git a/tests/unit/services/apigateway/test_apigateway_common.py b/tests/unit/services/apigateway/test_apigateway_common.py index a3df3f21ffa9c..93dddf6ef7b65 100644 --- a/tests/unit/services/apigateway/test_apigateway_common.py +++ b/tests/unit/services/apigateway/test_apigateway_common.py @@ -2,7 +2,7 @@ import unittest import xml from json import JSONDecodeError -from typing import Any, Dict +from typing import Any from unittest.mock import MagicMock, Mock import boto3 @@ -952,7 +952,7 @@ def test_construct_invocation_event(self): class TestRequestParameterResolver: def test_resolve_request_parameters(self): - integration: Dict[str, Any] = { + integration: dict[str, Any] = { "requestParameters": { "integration.request.path.pathParam": "method.request.path.id", "integration.request.querystring.baz": "method.request.querystring.baz", diff --git a/tests/unit/services/cloudformation/test_deployment_utils.py b/tests/unit/services/cloudformation/test_deployment_utils.py index d1df32f079edd..f2c5e55be0211 100644 --- a/tests/unit/services/cloudformation/test_deployment_utils.py +++ b/tests/unit/services/cloudformation/test_deployment_utils.py @@ -12,7 +12,7 @@ def test_nested_parameters_are_fixed(self): fixed_params = fix_boto_parameters_based_on_report(params, message) value = fixed_params["LaunchTemplate"]["Version"] assert value == "1" - assert type(value) == str + assert isinstance(value, str) def test_top_level_parameters_are_converted(self): params = {"Version": 1} @@ -21,4 +21,4 @@ def test_top_level_parameters_are_converted(self): fixed_params = fix_boto_parameters_based_on_report(params, message) value = fixed_params["Version"] assert value == "1" - assert type(value) == str + assert isinstance(value, str) diff --git a/tests/unit/services/cloudformation/test_provider_utils.py b/tests/unit/services/cloudformation/test_provider_utils.py index 5fec01d31d662..78ee6b73727d6 100644 --- a/tests/unit/services/cloudformation/test_provider_utils.py +++ b/tests/unit/services/cloudformation/test_provider_utils.py @@ -135,3 +135,68 @@ def test_lower_camelcase_to_pascalcase(self): } ], } + + def test_lower_camelcase_to_pascalcase_skip_keys(self): + original_dict = { + "Stages": [ + { + "Actions": [ + { + "Actiontypeid": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1", + }, + "Configuration": { + "S3bucket": "localstack-codepipeline-source-86a13a88", + "S3objectkey": "source-key", + "Subconfig": {"Subconfig1": "Foo", "Subconfig2": "bar"}, + }, + "Inputartifacts": [], + "Name": "S3Source", + "Namespace": "S3SourceVariables", + "Outputartifacts": [{"Name": "Artifact_Source_S3Source"}], + "Rolearn": "arn:aws:iam::096845016391:role/EcrPipelineStack-MyPipelineSourceS3SourceCodePipeli-YOoRQUZQe6WU", + "Runorder": 1, + } + ], + "Name": "Source", + } + ] + } + target_dict = { + "stages": [ + { + "actions": [ + { + "actiontypeid": { + "category": "Source", + "owner": "AWS", + "provider": "S3", + "version": "1", + }, + # The excluded key itself is transformed + # Its values are not + # Recursion stops, items at lower levels are not transformed as well + "configuration": { + "S3bucket": "localstack-codepipeline-source-86a13a88", + "S3objectkey": "source-key", + "Subconfig": {"Subconfig1": "Foo", "Subconfig2": "bar"}, + }, + "inputartifacts": [], + "name": "S3Source", + "namespace": "S3SourceVariables", + "outputartifacts": [{"name": "Artifact_Source_S3Source"}], + "rolearn": "arn:aws:iam::096845016391:role/EcrPipelineStack-MyPipelineSourceS3SourceCodePipeli-YOoRQUZQe6WU", + "runorder": 1, + } + ], + "name": "Source", + } + ] + } + converted_dict = utils.keys_pascalcase_to_lower_camelcase( + original_dict, skip_keys={"Configuration"} + ) + assert converted_dict == target_dict diff --git a/tests/unit/test_common.py b/tests/unit/test_common.py index 2988ecaff64b8..377e040b9256e 100644 --- a/tests/unit/test_common.py +++ b/tests/unit/test_common.py @@ -5,7 +5,7 @@ import threading import time import zipfile -from datetime import date, datetime, timezone +from datetime import UTC, date, datetime from zoneinfo import ZoneInfo import pytest @@ -75,7 +75,7 @@ def test_now(self): def test_now_utc(self): env = common.now_utc() - test = datetime.now(timezone.utc).timestamp() + test = datetime.now(UTC).timestamp() assert test == pytest.approx(env, 1) def test_is_number(self): @@ -94,7 +94,7 @@ def test_mktime(self): def test_mktime_with_tz(self): # see https://en.wikipedia.org/wiki/File:1000000000seconds.jpg - dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=timezone.utc) + dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=UTC) assert int(common.mktime(dt)) == 1000000000 dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=ZoneInfo("EST")) @@ -102,7 +102,7 @@ def test_mktime_with_tz(self): def test_mktime_millis_with_tz(self): # see https://en.wikipedia.org/wiki/File:1000000000 - dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=timezone.utc) + dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=UTC) assert int(common.mktime(dt, millis=True) / 1000) == 1000000000 dt = datetime(2001, 9, 9, 1, 46, 40, 0, tzinfo=ZoneInfo("EST")) diff --git a/tests/unit/test_dockerclient.py b/tests/unit/test_dockerclient.py index 03f9e5cdc63a8..146d86db33854 100644 --- a/tests/unit/test_dockerclient.py +++ b/tests/unit/test_dockerclient.py @@ -1,7 +1,6 @@ import json import logging import textwrap -from typing import List from unittest.mock import patch import pytest @@ -21,7 +20,7 @@ class TestDockerClient: - def _docker_cmd(self) -> List[str]: + def _docker_cmd(self) -> list[str]: """Return the string to be used for running Docker commands.""" return config.DOCKER_CMD.split() @@ -307,7 +306,7 @@ def test_compose_env_files(self, tmp_path): def list_in(a, b): - return len(a) <= len(b) and any((b[x : x + len(a)] == a for x in range(len(b) - len(a) + 1))) + return len(a) <= len(b) and any(b[x : x + len(a)] == a for x in range(len(b) - len(a) + 1)) class TestPortMappings: diff --git a/tests/unit/test_edge.py b/tests/unit/test_edge.py index 09eb91c580e46..14b2b07e18c4f 100644 --- a/tests/unit/test_edge.py +++ b/tests/unit/test_edge.py @@ -1,5 +1,3 @@ -from typing import List - import pytest import requests from pytest_httpserver.httpserver import HTTPServer @@ -9,7 +7,7 @@ from localstack.utils.net import get_free_tcp_port -def gateway_listen_value(httpserver: HTTPServer) -> List[HostAndPort]: +def gateway_listen_value(httpserver: HTTPServer) -> list[HostAndPort]: return [HostAndPort(host=httpserver.host, port=httpserver.port)] diff --git a/tests/unit/utils/analytics/test_metrics.py b/tests/unit/utils/analytics/test_metrics.py index 1695aeea340d7..781a2083d8270 100644 --- a/tests/unit/utils/analytics/test_metrics.py +++ b/tests/unit/utils/analytics/test_metrics.py @@ -31,7 +31,7 @@ def test_counter_reset(): counter.increment(value=5) counter.reset() collected = counter.collect() - assert collected == list(), f"Unexpected counter value: expected 0, got {collected}" + assert collected == [], f"Unexpected counter value: expected 0, got {collected}" def test_labeled_counter_increment(): diff --git a/tests/unit/utils/analytics/test_publisher.py b/tests/unit/utils/analytics/test_publisher.py index a74096b3f256d..4c24ebdaf14af 100644 --- a/tests/unit/utils/analytics/test_publisher.py +++ b/tests/unit/utils/analytics/test_publisher.py @@ -1,7 +1,6 @@ import datetime import threading from queue import Queue -from typing import List import pytest @@ -26,7 +25,7 @@ def test_basic(self): calls = Queue() class QueuePublisher(Publisher): - def publish(self, _events: List[Event]): + def publish(self, _events: list[Event]): calls.put(_events) buffer = PublisherBuffer(QueuePublisher(), flush_size=2, flush_interval=1000) @@ -62,7 +61,7 @@ def test_interval(self): calls = Queue() class QueuePublisher(Publisher): - def publish(self, _events: List[Event]): + def publish(self, _events: list[Event]): calls.put(_events) buffer = PublisherBuffer(QueuePublisher(), flush_size=10, flush_interval=1) diff --git a/tests/unit/utils/analytics/test_service_call_aggregator.py b/tests/unit/utils/analytics/test_service_call_aggregator.py index 765570a19a2f9..6fcb9c2d79164 100644 --- a/tests/unit/utils/analytics/test_service_call_aggregator.py +++ b/tests/unit/utils/analytics/test_service_call_aggregator.py @@ -1,6 +1,5 @@ import time from queue import Queue -from typing import List import dateutil.parser import pytest @@ -71,7 +70,7 @@ def mock_emit_payload(_payload): def test_integration(monkeypatch): - events: List[Event] = [] + events: list[Event] = [] def _handle(_event: Event): events.append(_event) diff --git a/tests/unit/utils/test_bootstrap.py b/tests/unit/utils/test_bootstrap.py index 9ff4d2e5fc8d8..b5f5a94ac07d0 100644 --- a/tests/unit/utils/test_bootstrap.py +++ b/tests/unit/utils/test_bootstrap.py @@ -1,6 +1,6 @@ import os from contextlib import contextmanager -from typing import Any, Dict +from typing import Any import pytest @@ -16,7 +16,7 @@ @contextmanager -def temporary_env(env: Dict[str, Any]): +def temporary_env(env: dict[str, Any]): old = os.environ.copy() try: os.environ.update(env) diff --git a/tests/unit/utils/test_collections.py b/tests/unit/utils/test_collections.py index adb2581e77460..f751ece2420ff 100644 --- a/tests/unit/utils/test_collections.py +++ b/tests/unit/utils/test_collections.py @@ -9,13 +9,14 @@ ImmutableList, convert_to_typed_dict, is_comma_delimited_list, + optional_list, select_from_typed_dict, ) class MyTypeDict(TypedDict): key_one: str - key_optional: Optional[str] + key_optional: str | None def test_select_from_typed_dict(): @@ -129,7 +130,7 @@ class TestTypedDict(TypedDict): def test_convert_to_typed_dict_with_union(): class TestTypedDict(TypedDict): - union_member: Union[str, int] + union_member: Union[str, int] # noqa test_dict = {"union_member": 1} @@ -140,7 +141,7 @@ class TestTypedDict(TypedDict): def test_convert_to_typed_dict_with_optional(): class TestTypedDict(TypedDict): - optional_member: Optional[str] + optional_member: Optional[str] # noqa test_dict = {"optional_member": 1} @@ -193,3 +194,16 @@ def test_is_comma_limited_list(): assert not is_comma_delimited_list("foo, bar baz") assert not is_comma_delimited_list("foo,") assert not is_comma_delimited_list("") + + +@pytest.mark.parametrize( + "condition,input,expected", + [ + (True, [1, 2, 3], [1, 2, 3]), + (False, [1, 2, 3], []), + (True, [], []), + (False, [], []), + ], +) +def test_optional_list(condition, input, expected): + assert optional_list(condition, input) == expected diff --git a/tests/unit/utils/test_scheduler.py b/tests/unit/utils/test_scheduler.py index 82bd0f8ffe5a6..7c58225e9afd0 100644 --- a/tests/unit/utils/test_scheduler.py +++ b/tests/unit/utils/test_scheduler.py @@ -1,7 +1,6 @@ import threading import time from concurrent.futures.thread import ThreadPoolExecutor -from typing import Tuple import pytest @@ -13,8 +12,8 @@ class DummyTask: def __init__(self, fn=None) -> None: super().__init__() self.i = 0 - self.invocations = list() - self.completions = list() + self.invocations = [] + self.completions = [] self.fn = fn def __call__(self, *args, **kwargs): @@ -40,7 +39,7 @@ def dispatcher(): class TestScheduler: @staticmethod - def create_and_start(dispatcher) -> Tuple[Scheduler, threading.Thread]: + def create_and_start(dispatcher) -> tuple[Scheduler, threading.Thread]: scheduler = Scheduler(executor=dispatcher) thread = threading.Thread(target=scheduler.run) thread.start()