diff --git a/scripts/update_snapshot_results.sh b/scripts/update_snapshot_results.sh index 6e02e683b6..efb1b5d660 100755 --- a/scripts/update_snapshot_results.sh +++ b/scripts/update_snapshot_results.sh @@ -5,6 +5,7 @@ cfn-lint test/fixtures/templates/integration/dynamic-references.yaml -e -c I --f cfn-lint test/fixtures/templates/integration/resources-cloudformation-init.yaml -e -c I --format json > test/fixtures/results/integration/resources-cloudformation-init.json cfn-lint test/fixtures/templates/integration/ref-no-value.yaml -e -c I --format json > test/fixtures/results/integration/ref-no-value.json cfn-lint test/fixtures/templates/integration/availability-zones.yaml -e -c I --format json > test/fixtures/results/integration/availability-zones.json +cfn-lint test/fixtures/templates/integration/getatt-types.yaml -e -c I --format json > test/fixtures/results/integration/getatt-types.json cfn-lint test/fixtures/templates/integration/aws-ec2-networkinterface.yaml -e -c I --format json > test/fixtures/results/integration/aws-ec2-networkinterface.json cfn-lint test/fixtures/templates/integration/aws-ec2-instance.yaml -e -c I --format json > test/fixtures/results/integration/aws-ec2-instance.json cfn-lint test/fixtures/templates/integration/aws-ec2-launchtemplate.yaml -e -c I --format json > test/fixtures/results/integration/aws-ec2-launchtemplate.json diff --git a/src/cfnlint/data/schemas/other/resources/update_policy.json b/src/cfnlint/data/schemas/other/resources/update_policy.json index b8ae53fbb9..233e608c9b 100644 --- a/src/cfnlint/data/schemas/other/resources/update_policy.json +++ b/src/cfnlint/data/schemas/other/resources/update_policy.json @@ -115,6 +115,9 @@ "MaxBatchSize": { "type": "integer" }, + "MinActiveInstancesPercent": { + "type": "integer" + }, "MinInstancesInService": { "type": "integer" }, diff --git a/src/cfnlint/data/schemas/other/step_functions/statemachine.json b/src/cfnlint/data/schemas/other/step_functions/statemachine.json index 4af8362232..e05659700a 100644 --- a/src/cfnlint/data/schemas/other/step_functions/statemachine.json +++ b/src/cfnlint/data/schemas/other/step_functions/statemachine.json @@ -290,7 +290,7 @@ "properties": { "ErrorEquals": { "items": { - "types": "string" + "type": "string" }, "type": "array" }, @@ -303,7 +303,7 @@ "ErrorEquals", "Next" ], - "types": "object" + "type": "object" }, "type": "array" }, @@ -418,7 +418,7 @@ }, "ErrorEquals": { "items": { - "types": "string" + "type": "string" }, "type": "array" }, @@ -434,7 +434,7 @@ "required": [ "ErrorEquals" ], - "types": "object" + "type": "object" }, "type": "array" }, @@ -481,7 +481,7 @@ "properties": { "ErrorEquals": { "items": { - "types": "string" + "type": "string" }, "type": "array" }, @@ -494,7 +494,7 @@ "ErrorEquals", "Next" ], - "types": "object" + "type": "object" }, "type": "array" }, @@ -540,7 +540,7 @@ }, "ErrorEquals": { "items": { - "types": "string" + "type": "string" }, "type": "array" }, @@ -556,7 +556,7 @@ "required": [ "ErrorEquals" ], - "types": "object" + "type": "object" }, "type": "array" }, @@ -799,7 +799,7 @@ "properties": { "ErrorEquals": { "items": { - "types": "string" + "type": "string" }, "type": "array" }, @@ -812,7 +812,7 @@ "ErrorEquals", "Next" ], - "types": "object" + "type": "object" }, "type": "array" }, @@ -880,7 +880,7 @@ }, "ErrorEquals": { "items": { - "types": "string" + "type": "string" }, "type": "array" }, @@ -896,7 +896,7 @@ "required": [ "ErrorEquals" ], - "types": "object" + "type": "object" }, "type": "array" }, diff --git a/src/cfnlint/jsonschema/_resolvers_cfn.py b/src/cfnlint/jsonschema/_resolvers_cfn.py index 9f376ac112..8b2be3e2ed 100644 --- a/src/cfnlint/jsonschema/_resolvers_cfn.py +++ b/src/cfnlint/jsonschema/_resolvers_cfn.py @@ -248,8 +248,8 @@ def join(validator: Validator, instance: Any) -> ResolutionResult: continue try: for value in _join_expansion(values_v, values): - yield delimiter.join(value), values_v, None - except ValueError: + yield delimiter.join([str(v) for v in value]), values_v, None + except (ValueError, TypeError): return diff --git a/src/cfnlint/rules/functions/GetAtt.py b/src/cfnlint/rules/functions/GetAtt.py index f33f3692f2..1780107d3a 100644 --- a/src/cfnlint/rules/functions/GetAtt.py +++ b/src/cfnlint/rules/functions/GetAtt.py @@ -129,9 +129,13 @@ def _resolve_getatt( schema_types = ensure_list(getatt_schema.get("type")) types = ensure_list(s.get("type")) - if is_types_compatible( - types, schema_types, validator.context.strict_types - ): + # GetAtt type checking is strict. + # It must match in all cases + if any(t in ["boolean", "integer", "boolean"] for t in types): + # this should be switched to validate the value of the + # property if it was available + continue + if is_types_compatible(types, schema_types, True): continue reprs = ", ".join(repr(type) for type in types) diff --git a/src/cfnlint/rules/outputs/Value.py b/src/cfnlint/rules/outputs/Value.py index 9d0bb6c41d..66f504f761 100644 --- a/src/cfnlint/rules/outputs/Value.py +++ b/src/cfnlint/rules/outputs/Value.py @@ -50,10 +50,7 @@ def validate(self, validator: Validator, _: Any, instance: Any, schema: Any): for err in validator.descend( value, schema={ - "type": ["array", "string"], - "items": { - "type": "string", - }, + "type": ["string"], }, path=key, property_path=key, diff --git a/test/fixtures/results/integration/getatt-types.json b/test/fixtures/results/integration/getatt-types.json new file mode 100644 index 0000000000..bcf56c7db9 --- /dev/null +++ b/test/fixtures/results/integration/getatt-types.json @@ -0,0 +1,243 @@ +[ + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "ee655930-182c-2be9-568e-6377b79b5798", + "Level": "Error", + "Location": { + "End": { + "ColumnNumber": 12, + "LineNumber": 18 + }, + "Path": [ + "Resources", + "SsmParameter", + "Properties", + "Value", + "Fn::GetAtt" + ], + "Start": { + "ColumnNumber": 7, + "LineNumber": 18 + } + }, + "Message": "{'Fn::GetAtt': ['CapacityReservation', 'InstanceCount']} is not of type 'string'", + "ParentId": null, + "Rule": { + "Description": "Validates that GetAtt parameters are to valid resources and properties of those resources", + "Id": "E1010", + "ShortDescription": "GetAtt validation of parameters", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "76c89a87-5cd1-4dcd-1050-b5dc891de1fe", + "Level": "Error", + "Location": { + "End": { + "ColumnNumber": 69, + "LineNumber": 29 + }, + "Path": [ + "Outputs", + "SubWithGetAtt", + "Value", + "Fn::Sub", + 1, + "InstanceCount", + "Fn::GetAtt" + ], + "Start": { + "ColumnNumber": 57, + "LineNumber": 29 + } + }, + "Message": "{'Fn::GetAtt': ['CapacityReservation', 'InstanceCount']} is not of type 'string'", + "ParentId": null, + "Rule": { + "Description": "Make sure that output values have a type of string", + "Id": "E6101", + "ShortDescription": "Validate that outputs values are a string", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "efe80ed4-7370-e389-6d0f-9e5234a86db9", + "Level": "Error", + "Location": { + "End": { + "ColumnNumber": 10, + "LineNumber": 31 + }, + "Path": [ + "Outputs", + "Ipv4NetmaskLength", + "Value", + "Fn::GetAtt" + ], + "Start": { + "ColumnNumber": 5, + "LineNumber": 31 + } + }, + "Message": "{'Fn::GetAtt': ['CapacityReservation', 'InstanceCount']} is not of type 'string'", + "ParentId": null, + "Rule": { + "Description": "Make sure that output values have a type of string", + "Id": "E6101", + "ShortDescription": "Validate that outputs values are a string", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "75ebbb04-db20-4d32-4546-8fe0b984ef73", + "Level": "Error", + "Location": { + "End": { + "ColumnNumber": 10, + "LineNumber": 33 + }, + "Path": [ + "Outputs", + "String", + "Value", + "Fn::Sub" + ], + "Start": { + "ColumnNumber": 5, + "LineNumber": 33 + } + }, + "Message": "'CapacityReservation.InstanceCount' is not of type 'string'", + "ParentId": null, + "Rule": { + "Description": "Make sure that output values have a type of string", + "Id": "E6101", + "ShortDescription": "Validate that outputs values are a string", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "5dd605a3-2dee-84bd-905b-ddc1a10cc24f", + "Level": "Informational", + "Location": { + "End": { + "ColumnNumber": 22, + "LineNumber": 35 + }, + "Path": [ + "Outputs", + "Join", + "Value", + "Fn::Join", + 0 + ], + "Start": { + "ColumnNumber": 20, + "LineNumber": 35 + } + }, + "Message": "Prefer using Fn::Sub over Fn::Join with an empty delimiter", + "ParentId": null, + "Rule": { + "Description": "Prefer a sub instead of Join when using a join delimiter that is empty", + "Id": "I1022", + "ShortDescription": "Use Sub instead of Join", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "518fa518-bd82-b365-8ec2-c48ae8581f3e", + "Level": "Error", + "Location": { + "End": { + "ColumnNumber": 10, + "LineNumber": 37 + }, + "Path": [ + "Outputs", + "JoinWithGetAtt", + "Value", + "Fn::Join", + 1, + 0, + "Fn::GetAtt" + ], + "Start": { + "ColumnNumber": 5, + "LineNumber": 37 + } + }, + "Message": "{'Fn::GetAtt': ['CapacityReservation', 'InstanceCount']} is not of type 'string'", + "ParentId": null, + "Rule": { + "Description": "Make sure that output values have a type of string", + "Id": "E6101", + "ShortDescription": "Validate that outputs values are a string", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "59af0561-c0a4-cef0-bca5-bcd57975d7b3", + "Level": "Informational", + "Location": { + "End": { + "ColumnNumber": 22, + "LineNumber": 37 + }, + "Path": [ + "Outputs", + "JoinWithGetAtt", + "Value", + "Fn::Join", + 0 + ], + "Start": { + "ColumnNumber": 20, + "LineNumber": 37 + } + }, + "Message": "Prefer using Fn::Sub over Fn::Join with an empty delimiter", + "ParentId": null, + "Rule": { + "Description": "Prefer a sub instead of Join when using a join delimiter that is empty", + "Id": "I1022", + "ShortDescription": "Use Sub instead of Join", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html" + } + }, + { + "Filename": "test/fixtures/templates/integration/getatt-types.yaml", + "Id": "3b709659-f299-68e5-3bac-f9835c487de3", + "Level": "Error", + "Location": { + "End": { + "ColumnNumber": 10, + "LineNumber": 41 + }, + "Path": [ + "Outputs", + "BooleanGetAtt", + "Value", + "Fn::GetAtt" + ], + "Start": { + "ColumnNumber": 5, + "LineNumber": 41 + } + }, + "Message": "{'Fn::GetAtt': ['CapacityReservation', 'EphemeralStorage']} is not of type 'string'", + "ParentId": null, + "Rule": { + "Description": "Make sure that output values have a type of string", + "Id": "E6101", + "ShortDescription": "Validate that outputs values are a string", + "Source": "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html" + } + } +] diff --git a/test/fixtures/templates/integration/getatt-types.yaml b/test/fixtures/templates/integration/getatt-types.yaml new file mode 100644 index 0000000000..3c15eb5615 --- /dev/null +++ b/test/fixtures/templates/integration/getatt-types.yaml @@ -0,0 +1,43 @@ +Parameters: + ANumber: + Type: Number + Default: 1 +Transform: 'AWS::LanguageExtensions' +Resources: + CapacityReservation: + Type: AWS::EC2::CapacityReservation + Properties: + AvailabilityZone: !Select [0, !GetAZs ""] + InstanceCount: 1 + InstanceType: t2.micro + InstancePlatform: Linux/UNIX + SsmParameter: + Type: AWS::SSM::Parameter + Properties: + Type: String + Value: !GetAtt CapacityReservation.InstanceCount # #/Value: expected type: String, found: Integer +Outputs: + Value: + Value: 1 # OK + Sub: + Value: !Sub ["${InstanceCount}", {"InstanceCount": 1}] # OK + RefWithParameter: + Value: !Ref ANumber # OK + SubWithParameter: + Value: !Sub "${ANumber}" # OK + SubWithGetAtt: + Value: !Sub ["${InstanceCount}", {"InstanceCount": {"Fn::GetAtt": ["CapacityReservation", "InstanceCount"]}}] # Template error: every value of the context object of every Fn::Sub object must be a string or a function that returns a string + Ipv4NetmaskLength: + Value: !GetAtt CapacityReservation.InstanceCount # Template format error: Every Value member must be a string. + String: + Value: !Sub "${CapacityReservation.InstanceCount}" # Template error: variable CapacityReservation.InstanceCount in Fn::Sub expression does not resolve to a string + Join: + Value: !Join [ "", [1]] # OK + JoinWithGetAtt: + Value: !Join [ "", [!GetAtt CapacityReservation.InstanceCount]] # Template error: every Fn::Join object requires two parameters, (1) a string delimiter and (2) a list of strings to be joined or a function that returns a list of strings (such as Fn::GetAZs) to be joined. + Boolean: + Value: true # OK + BooleanGetAtt: + Value: !GetAtt CapacityReservation.EphemeralStorage # Template format error: Every Value member must be a string. + Length: + Value: {Fn::Length: [1, 2, 3]} # OK diff --git a/test/integration/test_integration_templates.py b/test/integration/test_integration_templates.py index 99131501c7..761ff40f57 100644 --- a/test/integration/test_integration_templates.py +++ b/test/integration/test_integration_templates.py @@ -47,6 +47,11 @@ class TestQuickStartTemplates(BaseCliTestCase): ), "exit_code": 2, }, + { + "filename": ("test/fixtures/templates/integration/getatt-types.yaml"), + "results_filename": ("test/fixtures/results/integration/getatt-types.json"), + "exit_code": 10, + }, { "filename": ( "test/fixtures/templates/integration/aws-ec2-networkinterface.yaml" @@ -87,11 +92,6 @@ def test_templates(self): ConfigMixIn( [], include_checks=["I"], - configure_rules={ - "E3012": { - "strict": True, - } - }, include_experimental=True, ) ) diff --git a/test/unit/rules/functions/test_getatt.py b/test/unit/rules/functions/test_getatt.py index e1c54f0dfa..aedc3af866 100644 --- a/test/unit/rules/functions/test_getatt.py +++ b/test/unit/rules/functions/test_getatt.py @@ -7,12 +7,9 @@ import pytest -from cfnlint.context import create_context_for_template -from cfnlint.context.context import Transforms -from cfnlint.jsonschema import CfnTemplateValidator, ValidationError +from cfnlint.jsonschema import ValidationError from cfnlint.rules import CfnLintKeyword from cfnlint.rules.functions.GetAtt import GetAtt -from cfnlint.template import Template @pytest.fixture(scope="module") @@ -21,27 +18,19 @@ def rule(): yield rule -@pytest.fixture(scope="module") -def cfn(): - return Template( - "", - { - "Resources": { - "MyBucket": {"Type": "AWS::S3::Bucket"}, - "MyCodePipeline": {"Type": "AWS::CodePipeline::Pipeline"}, - }, - "Parameters": { - "MyResourceParameter": {"Type": "String", "Default": "MyBucket"}, - "MyAttributeParameter": {"Type": "String", "AllowedValues": ["Arn"]}, - }, - }, - regions=["us-east-1"], - ) - +_template = { + "Resources": { + "MyBucket": {"Type": "AWS::S3::Bucket"}, + "MyCodePipeline": {"Type": "AWS::CodePipeline::Pipeline"}, + }, + "Parameters": { + "MyResourceParameter": {"Type": "String", "Default": "MyBucket"}, + "MyAttributeParameter": {"Type": "String", "AllowedValues": ["Arn"]}, + }, +} -@pytest.fixture(scope="module") -def context(cfn): - return create_context_for_template(cfn) +_template_with_transform = _template.copy() +_template_with_transform["Transform"] = "AWS::LanguageExtensions" class _Pass(CfnLintKeyword): @@ -66,13 +55,13 @@ def validate(self, validator, s, instance, schema): @pytest.mark.parametrize( - "name,instance,schema,context_evolve,child_rules,expected", + "name,instance,schema,template,child_rules,expected", [ ( "Valid GetAtt with a good attribute", {"Fn::GetAtt": ["MyBucket", "Arn"]}, {"type": "string"}, - {}, + _template, {}, [], ), @@ -80,7 +69,7 @@ def validate(self, validator, s, instance, schema): "Invalid GetAtt with bad attribute", {"Fn::GetAtt": ["MyBucket", "foo"]}, {"type": "string"}, - {}, + _template, {}, [ ValidationError( @@ -99,7 +88,7 @@ def validate(self, validator, s, instance, schema): "Invalid GetAtt with bad resource name", {"Fn::GetAtt": ["Foo", "bar"]}, {"type": "string"}, - {}, + _template, {}, [ ValidationError( @@ -114,7 +103,7 @@ def validate(self, validator, s, instance, schema): "Invalid GetAtt with a bad type", {"Fn::GetAtt": {"foo": "bar"}}, {"type": "string"}, - {}, + _template, {}, [ ValidationError( @@ -129,7 +118,7 @@ def validate(self, validator, s, instance, schema): "Invalid GetAtt with a bad response type", {"Fn::GetAtt": "MyBucket.Arn"}, {"type": "array"}, - {}, + _template, {}, [ ValidationError( @@ -144,7 +133,7 @@ def validate(self, validator, s, instance, schema): "Invalid GetAtt with a bad response type and multiple types", {"Fn::GetAtt": "MyBucket.Arn"}, {"type": ["array", "object"]}, - {}, + _template, {}, [ ValidationError( @@ -159,37 +148,35 @@ def validate(self, validator, s, instance, schema): "Valid GetAtt with integer to string", {"Fn::GetAtt": "MyCodePipeline.Version"}, {"type": ["integer"]}, - { - "strict_types": False, - }, + _template, {}, [], ), - ( - "Invalid GetAtt with integer to string", - {"Fn::GetAtt": "MyCodePipeline.Version"}, - {"type": ["integer"]}, - { - "strict_types": True, - }, - {}, - [ - ValidationError( - ( - "{'Fn::GetAtt': 'MyCodePipeline.Version'} " - "is not of type 'integer'" - ), - path=deque(["Fn::GetAtt"]), - schema_path=deque(["type"]), - validator="fn_getatt", - ) - ], - ), + # ( + # "Invalid GetAtt with integer to string", + # {"Fn::GetAtt": "MyCodePipeline.Version"}, + # {"type": ["integer"]}, + # { + # "strict_types": True, + # }, + # {}, + # [ + # ValidationError( + # ( + # "{'Fn::GetAtt': 'MyCodePipeline.Version'} " + # "is not of type 'integer'" + # ), + # path=deque(["Fn::GetAtt"]), + # schema_path=deque(["type"]), + # validator="fn_getatt", + # ) + # ], + # ), ( "Valid GetAtt with one good response type", {"Fn::GetAtt": "MyBucket.Arn"}, {"type": ["array", "string"]}, - {}, + _template, {}, [], ), @@ -197,7 +184,7 @@ def validate(self, validator, s, instance, schema): "Valid Ref in GetAtt for resource", {"Fn::GetAtt": [{"Ref": "MyResourceParameter"}, "Arn"]}, {"type": "string"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + _template_with_transform, {}, [], ), @@ -205,7 +192,7 @@ def validate(self, validator, s, instance, schema): "Valid Ref in GetAtt for attribute", {"Fn::GetAtt": ["MyBucket", {"Ref": "MyAttributeParameter"}]}, {"type": "string"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + _template_with_transform, {}, [], ), @@ -213,7 +200,7 @@ def validate(self, validator, s, instance, schema): "Invalid Ref in GetAtt for attribute", {"Fn::GetAtt": ["MyBucket", {"Ref": "MyResourceParameter"}]}, {"type": "string"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + _template_with_transform, {}, [ ValidationError( @@ -232,7 +219,7 @@ def validate(self, validator, s, instance, schema): "Invalid Ref in GetAtt for attribute", {"Fn::GetAtt": [{"Ref": "MyAttributeParameter"}, "Arn"]}, {"type": "string"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + _template_with_transform, {}, [ ValidationError( @@ -250,7 +237,7 @@ def validate(self, validator, s, instance, schema): "Valid GetAtt with child rules", {"Fn::GetAtt": ["MyBucket", "Arn"]}, {"type": "string"}, - {}, + _template, { "AAAAA": _Pass(), "BBBBB": _Fail(), @@ -259,13 +246,12 @@ def validate(self, validator, s, instance, schema): [ValidationError("Fail")], ), ], + indirect=["template"], ) def test_validate( - name, instance, schema, context_evolve, child_rules, expected, rule, context, cfn + name, instance, schema, template, child_rules, expected, validator, rule ): - context = context.evolve(**context_evolve) rule.child_rules = child_rules - validator = CfnTemplateValidator({}, context=context, cfn=cfn) errs = list(rule.fn_getatt(validator, schema, instance, {})) assert errs == expected, f"Test {name!r} got {errs!r}" diff --git a/test/unit/rules/functions/test_length.py b/test/unit/rules/functions/test_length.py index a6b88a4bb0..6ddc0cce85 100644 --- a/test/unit/rules/functions/test_length.py +++ b/test/unit/rules/functions/test_length.py @@ -7,35 +7,18 @@ import pytest -from cfnlint.context import create_context_for_template -from cfnlint.context.context import Transforms -from cfnlint.jsonschema import CfnTemplateValidator, ValidationError +from cfnlint.jsonschema import ValidationError from cfnlint.rules.functions.Length import Length -from cfnlint.template import Template -@pytest.fixture(scope="module") +@pytest.fixture def rule(): rule = Length() yield rule -@pytest.fixture(scope="module") -def cfn(): - return Template( - "", - {}, - regions=["us-east-1"], - ) - - -@pytest.fixture(scope="module") -def context(cfn): - return create_context_for_template(cfn) - - @pytest.mark.parametrize( - "name,instance,schema,context_evolve,expected", + "name,instance,schema,template,expected", [ ( "Fn::Length is not supported", @@ -59,14 +42,14 @@ def context(cfn): "Fn::Length valid structure", {"Fn::Length": []}, {"type": "integer"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [], ), ( "Fn::Length invalid type", {"Fn::Length": "foo"}, {"type": "integer"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [ ValidationError( "'foo' is not of type 'array'", @@ -81,7 +64,7 @@ def context(cfn): "Fn::Length invalid output type", {"Fn::Length": ["foo"]}, {"type": "array"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [ ValidationError( "{'Fn::Length': ['foo']} is not of type 'array'", @@ -96,34 +79,33 @@ def context(cfn): "Fn::Length using valid function", {"Fn::Length": {"Fn::GetAZs": ""}}, {"type": "integer"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [], ), ( "Fn::Length using valid functions in array", {"Fn::Length": [{"Ref": "MyResource"}]}, {"type": "integer"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [], ), ( "Fn::Length is not supported", {"Fn::Length": []}, {"type": "integer"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [], ), ( "Fn::Length output while a number can be a string", {"Fn::Length": []}, {"type": "string"}, - {"transforms": Transforms(["AWS::LanguageExtensions"])}, + {"Transform": ["AWS::LanguageExtensions"]}, [], ), ], + indirect=["template"], ) -def test_validate(name, instance, schema, context_evolve, expected, rule, context, cfn): - context = context.evolve(**context_evolve) - validator = CfnTemplateValidator(context=context, cfn=cfn) +def test_validate(name, instance, schema, expected, validator, rule): errs = list(rule.fn_length(validator, schema, instance, {})) assert errs == expected, f"Test {name!r} got {errs!r}" diff --git a/test/unit/rules/outputs/test_value.py b/test/unit/rules/outputs/test_value.py index ec1b481d45..6fdb3b8d5f 100644 --- a/test/unit/rules/outputs/test_value.py +++ b/test/unit/rules/outputs/test_value.py @@ -103,7 +103,7 @@ def validator(cfn, context): }, [ ValidationError( - "1.0 is not of type 'array', 'string'", + "1.0 is not of type 'string'", validator="type", schema_path=deque(["type"]), path=deque(["Value"]), @@ -117,7 +117,7 @@ def validator(cfn, context): }, [ ValidationError( - "1 is not of type 'array', 'string'", + "1 is not of type 'string'", validator="type", schema_path=deque(["type"]), path=deque(["Value"]), @@ -129,7 +129,7 @@ def validator(cfn, context): {"Value": True}, [ ValidationError( - "True is not of type 'array', 'string'", + "True is not of type 'string'", validator="type", schema_path=deque(["type"]), path=deque(["Value"]), @@ -141,10 +141,10 @@ def validator(cfn, context): {"Value": [{}]}, [ ValidationError( - "{} is not of type 'string'", + "[{}] is not of type 'string'", validator="type", - schema_path=deque(["items", "type"]), - path=deque(["Value", 0]), + schema_path=deque(["type"]), + path=deque(["Value"]), rule=Value(), ) ], @@ -153,7 +153,7 @@ def validator(cfn, context): {"Value": {"foo": "bar"}}, [ ValidationError( - "{'foo': 'bar'} is not of type 'array', 'string'", + "{'foo': 'bar'} is not of type 'string'", validator="type", schema_path=deque(["type"]), path=deque(["Value"]),