Repository: codecentric/hikaku Branch: master Commit: b8a754f91b52 Files: 305 Total size: 793.0 KB Directory structure: gitextract_0lqdq178/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── question.md │ └── dependabot.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── core/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ ├── Hikaku.kt │ │ │ ├── HikakuConfig.kt │ │ │ ├── SupportedFeatures.kt │ │ │ ├── converters/ │ │ │ │ ├── AbstractEndpointConverter.kt │ │ │ │ ├── ClassLocator.kt │ │ │ │ ├── EndpointConverter.kt │ │ │ │ └── EndpointConverterException.kt │ │ │ ├── endpoints/ │ │ │ │ ├── Endpoint.kt │ │ │ │ ├── HeaderParameter.kt │ │ │ │ ├── HttpMethod.kt │ │ │ │ ├── MatrixParameter.kt │ │ │ │ ├── PathParameter.kt │ │ │ │ └── QueryParameter.kt │ │ │ ├── extensions/ │ │ │ │ ├── ClassExtensions.kt │ │ │ │ ├── FileExtensions.kt │ │ │ │ └── PathExtensions.kt │ │ │ └── reporters/ │ │ │ ├── CommandLineReporter.kt │ │ │ ├── MatchResult.kt │ │ │ ├── NoOperationReporter.kt │ │ │ └── Reporter.kt │ │ └── resources/ │ │ └── .gitemptydir │ └── test/ │ ├── kotlin/ │ │ └── de/ │ │ └── codecentric/ │ │ └── hikaku/ │ │ ├── HikakuTest.kt │ │ └── extensions/ │ │ ├── ClassExtensionsTest.kt │ │ ├── FileExtensionsTest.kt │ │ └── PathExtensionsTest.kt │ └── resources/ │ └── test_file.txt ├── docs/ │ ├── config.md │ └── features.md ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── install-jdk.sh ├── jax-rs/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── jaxrs/ │ │ │ └── JaxRsConverter.kt │ │ └── resources/ │ │ └── .gitemptydir │ └── test/ │ ├── kotlin/ │ │ ├── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── jaxrs/ │ │ │ ├── JaxRsConverterConsumesTest.kt │ │ │ ├── JaxRsConverterDeprecationTest.kt │ │ │ ├── JaxRsConverterHeaderParametersTest.kt │ │ │ ├── JaxRsConverterHttpMethodsTest.kt │ │ │ ├── JaxRsConverterMatrixParametersTest.kt │ │ │ ├── JaxRsConverterPackageDefinitionTest.kt │ │ │ ├── JaxRsConverterPathParametersTest.kt │ │ │ ├── JaxRsConverterPathTests.kt │ │ │ ├── JaxRsConverterProducesTest.kt │ │ │ └── JaxRsConverterQueryParameterTest.kt │ │ └── test/ │ │ └── jaxrs/ │ │ ├── consumes/ │ │ │ ├── functiondeclarationoverwritesclassdeclaration/ │ │ │ │ └── FunctionDeclarationOverwritesClassDeclaration.kt │ │ │ ├── multiplemediatypesonclass/ │ │ │ │ └── MultipleMediaTypesOnClass.kt │ │ │ ├── multiplemediatypesonfunction/ │ │ │ │ └── MultipleMediaTypesOnFunction.kt │ │ │ ├── noannotation/ │ │ │ │ └── NoAnnotation.kt │ │ │ ├── singlemediatypeonclass/ │ │ │ │ └── ProducesOnClass.kt │ │ │ ├── singlemediatypeonfunction/ │ │ │ │ └── ProducesOnFunction.kt │ │ │ ├── singlemediatypewithoutrequestbody/ │ │ │ │ └── SingleMediaTypeWithoutRequestBody.kt │ │ │ └── singlemediatypewithoutrequestbodybutotherannotatedparameter/ │ │ │ └── SingleMediaTypeWithoutRequestBodyButOtherAnnotatedParameter.kt │ │ ├── deprecation/ │ │ │ ├── none/ │ │ │ │ └── NoDeprecation.kt │ │ │ ├── onclass/ │ │ │ │ └── DeprecationOnClass.kt │ │ │ └── onfunction/ │ │ │ └── DeprecationOnFunction.kt │ │ ├── headerparameters/ │ │ │ └── onfunction/ │ │ │ └── HeaderParametersOnFunction.kt │ │ ├── httpmethod/ │ │ │ ├── allmethods/ │ │ │ │ └── AllHttpMethods.kt │ │ │ └── noannotation/ │ │ │ └── NoAnnotation.kt │ │ ├── matrixparameters/ │ │ │ └── onfunction/ │ │ │ └── MatrixParametersOnFunction.kt │ │ ├── path/ │ │ │ ├── nestedpath/ │ │ │ │ └── NestedPath.kt │ │ │ ├── nestedpathwithoutleadingslash/ │ │ │ │ └── NestedPathWithoutLeadingSlash.kt │ │ │ ├── nopathonclass/ │ │ │ │ └── NoPathAnnotationOnclass.kt │ │ │ ├── simplepath/ │ │ │ │ └── SimplePath.kt │ │ │ └── simplepathwithoutleadingslash/ │ │ │ └── SimplePathWithoutLeadingSlash.kt │ │ ├── pathparameters/ │ │ │ ├── nopathparameter/ │ │ │ │ └── NoPathParameter.kt │ │ │ └── onfunction/ │ │ │ └── PathParameterOnFunction.kt │ │ ├── produces/ │ │ │ ├── functiondeclarationoverwritesclassdeclaration/ │ │ │ │ └── FunctionDeclarationOverwritesClassDeclaration.kt │ │ │ ├── multiplemediatypesonclass/ │ │ │ │ └── MultipleMediaTypesOnClass.kt │ │ │ ├── multiplemediatypesonfunction/ │ │ │ │ └── MultipleMediaTypesOnFunction.kt │ │ │ ├── noannotation/ │ │ │ │ └── NoAnnotation.kt │ │ │ ├── singlemediatypeonclass/ │ │ │ │ └── ProducesOnClass.kt │ │ │ ├── singlemediatypeonfunction/ │ │ │ │ └── ProducesOnFunction.kt │ │ │ └── singlemediatypewithoutreturntype/ │ │ │ └── SingleMediaTypeWithoutReturnType.kt │ │ └── queryparameters/ │ │ └── onfunction/ │ │ └── QueryParameterOnFunction.kt │ └── resources/ │ └── .gitemptydir ├── micronaut/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── micronaut/ │ │ │ └── MicronautConverter.kt │ │ └── resources/ │ │ └── .gitemptydir │ └── test/ │ ├── kotlin/ │ │ ├── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── micronaut/ │ │ │ ├── MicronautConverterConsumesTest.kt │ │ │ ├── MicronautConverterDeprecationTest.kt │ │ │ ├── MicronautConverterHeaderParameterTest.kt │ │ │ ├── MicronautConverterPackageDefinitionTest.kt │ │ │ ├── MicronautConverterPathParameterTest.kt │ │ │ ├── MicronautConverterPathTest.kt │ │ │ ├── MicronautConverterProducesTest.kt │ │ │ └── MicronautConverterQueryParameterTest.kt │ │ └── test/ │ │ └── micronaut/ │ │ ├── Todo.kt │ │ ├── consumes/ │ │ │ ├── default/ │ │ │ │ └── ConsumesDefaultMediaTypeTestController.kt │ │ │ ├── onclass/ │ │ │ │ ├── consumesoverridescontroller/ │ │ │ │ │ ├── multiplemediatypes/ │ │ │ │ │ │ └── ConsumesMultipleMediaTypesTestController.kt │ │ │ │ │ └── singlemediatype/ │ │ │ │ │ └── ConsumesSingleMediaTypeTestController.kt │ │ │ │ └── onlycontroller/ │ │ │ │ ├── multiplemediatypes/ │ │ │ │ │ └── ConsumesMultipleMediaTypesTestController.kt │ │ │ │ └── singlemediatype/ │ │ │ │ └── ConsumesSingleMediaTypeTestController.kt │ │ │ └── onfunction/ │ │ │ ├── consumesoverridescontroller/ │ │ │ │ ├── multiplemediatypes/ │ │ │ │ │ └── ConsumesMultipleMediaTypesTestController.kt │ │ │ │ └── singlemediatype/ │ │ │ │ └── ConsumesSingleMediaTypeTestController.kt │ │ │ └── onlyconsumes/ │ │ │ ├── multiplemediatypes/ │ │ │ │ └── ConsumesMultipleMediaTypesTestController.kt │ │ │ └── singlemediatype/ │ │ │ └── ConsumesSingleMediaTypeTestController.kt │ │ ├── deprecation/ │ │ │ ├── none/ │ │ │ │ └── NoDeprecation.kt │ │ │ ├── onclass/ │ │ │ │ └── DeprecationOnClass.kt │ │ │ └── onfunction/ │ │ │ └── DeprecationOnFunction.kt │ │ ├── headerparameters/ │ │ │ ├── optional/ │ │ │ │ └── HeaderParameterTestController.kt │ │ │ └── required/ │ │ │ └── HeaderParameterTestController.kt │ │ ├── path/ │ │ │ ├── combinedcontrollerandhttpmethodannotation/ │ │ │ │ ├── delete/ │ │ │ │ │ └── CombinedControllerAnnotationWithDeletePathTestController.kt │ │ │ │ ├── get/ │ │ │ │ │ └── CombinedControllerAnnotationWithGetPathTestController.kt │ │ │ │ ├── head/ │ │ │ │ │ └── CombinedControllerAnnotationWithHeadPathTestController.kt │ │ │ │ ├── options/ │ │ │ │ │ └── CombinedControllerAnnotationWithOptionsPathTestController.kt │ │ │ │ ├── patch/ │ │ │ │ │ └── CombinedControllerAnnotationWithPatchPathTestController.kt │ │ │ │ ├── post/ │ │ │ │ │ └── CombinedControllerAnnotationWithPostPathTestController.kt │ │ │ │ └── put/ │ │ │ │ └── CombinedControllerAnnotationWithPutPathTestController.kt │ │ │ ├── firstpathsegmentwithoutleadingslash/ │ │ │ │ └── FirstPathSegmentWithoutLeadingSlashTestController.kt │ │ │ ├── innerpathsegmentwithoutleadingslash/ │ │ │ │ └── InnerPathSegmentWithoutLeadingSlashTestController.kt │ │ │ ├── nohttpmethodannotation/ │ │ │ │ └── NoHttpMethodAnnotationTestController.kt │ │ │ └── onlycontrollerannotation/ │ │ │ ├── delete/ │ │ │ │ └── OnlyControllerAnnotationWithDeletePathTestController.kt │ │ │ ├── get/ │ │ │ │ └── OnlyControllerAnnotationWithGetPathTestController.kt │ │ │ ├── head/ │ │ │ │ └── OnlyControllerAnnotationWithHeadPathTestController.kt │ │ │ ├── options/ │ │ │ │ └── OnlyControllerAnnotationWithOptionsPathTestController.kt │ │ │ ├── patch/ │ │ │ │ └── OnlyControllerAnnotationWithPatchPathTestController.kt │ │ │ ├── post/ │ │ │ │ └── OnlyControllerAnnotationWithPostPathTestController.kt │ │ │ └── put/ │ │ │ └── OnlyControllerAnnotationWithPutPathTestController.kt │ │ ├── pathparameters/ │ │ │ ├── annotation/ │ │ │ │ ├── name/ │ │ │ │ │ └── PathParameterDefinedByAnnotationTestController.kt │ │ │ │ └── value/ │ │ │ │ └── PathParameterDefinedByAnnotationTestController.kt │ │ │ └── variable/ │ │ │ └── PathParameterDefinedByVariableNameTestController.kt │ │ ├── produces/ │ │ │ ├── default/ │ │ │ │ └── DefaultTestController.kt │ │ │ ├── onclass/ │ │ │ │ ├── onlycontroller/ │ │ │ │ │ ├── multiplemediatypes/ │ │ │ │ │ │ └── ProducesMultipleMediaTypesTestController.kt │ │ │ │ │ └── singlemediatype/ │ │ │ │ │ └── ProducesSingleMediaTypeTestController.kt │ │ │ │ └── producesoverridescontroller/ │ │ │ │ ├── multiplemediatypes/ │ │ │ │ │ └── ProducesMultipleMediaTypesTestController.kt │ │ │ │ └── singlemediatype/ │ │ │ │ └── ProducesSingleMediaTypeTestController.kt │ │ │ └── onfunction/ │ │ │ ├── onlyproduces/ │ │ │ │ ├── multiplemediatypes/ │ │ │ │ │ └── ProducesMultipleMediaTypesTestController.kt │ │ │ │ └── singlemediatype/ │ │ │ │ └── ProducesSingleMediaTypeTestController.kt │ │ │ └── producesoverridescontroller/ │ │ │ ├── multiplemediatypes/ │ │ │ │ └── ProducesMultipleMediaTypesTestController.kt │ │ │ └── singlemediatype/ │ │ │ └── ProducesSingleMediaTypeTestController.kt │ │ └── queryparameters/ │ │ ├── optional/ │ │ │ └── QueryParameterTestController.kt │ │ └── required/ │ │ ├── annotation/ │ │ │ └── QueryParameterTestController.kt │ │ └── withoutannotation/ │ │ └── QueryParameterTestController.kt │ └── resources/ │ └── resources/ │ └── .gitemptydir ├── openapi/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── openapi/ │ │ │ ├── OpenApiConverter.kt │ │ │ ├── extensions/ │ │ │ │ ├── PathItemExtensions.kt │ │ │ │ └── ReferencedSchema.kt │ │ │ └── extractors/ │ │ │ ├── ConsumesExtractor.kt │ │ │ ├── HeaderParameterExtractor.kt │ │ │ ├── PathParameterExtractor.kt │ │ │ ├── ProducesExtractor.kt │ │ │ └── QueryParameterExtractor.kt │ │ └── resources/ │ │ └── .gitemptydir │ └── test/ │ ├── kotlin/ │ │ └── de/ │ │ └── codecentric/ │ │ └── hikaku/ │ │ └── converters/ │ │ └── openapi/ │ │ ├── OpenApiConverterConsumesTest.kt │ │ ├── OpenApiConverterDeprecationTest.kt │ │ ├── OpenApiConverterEndpointTest.kt │ │ ├── OpenApiConverterHeaderParameterTest.kt │ │ ├── OpenApiConverterInvalidInputTest.kt │ │ ├── OpenApiConverterPathParameterTest.kt │ │ ├── OpenApiConverterProducesTest.kt │ │ └── OpenApiConverterQueryParameterTest.kt │ └── resources/ │ ├── consumes/ │ │ ├── consumes_inline.yaml │ │ └── consumes_requestbody_in_components.yaml │ ├── deprecation/ │ │ ├── deprecation_none.yaml │ │ └── deprecation_operation.yaml │ ├── endpoints/ │ │ ├── endpoints_all_http_methods.yaml │ │ ├── endpoints_two_different_paths.yaml │ │ └── endpoints_two_nested_paths.yaml │ ├── header_parameter/ │ │ ├── common_header_parameter_in_components.yaml │ │ ├── common_header_parameter_inline.yaml │ │ ├── header_parameter_in_components.yaml │ │ └── header_parameter_inline.yaml │ ├── invalid_input/ │ │ ├── empty_file.yaml │ │ ├── syntax_error.yaml │ │ └── whitespaces_only_file.yaml │ ├── path_parameter/ │ │ ├── common_path_parameter_in_components.yaml │ │ ├── common_path_parameter_inline.yaml │ │ ├── path_parameter_in_components.yaml │ │ └── path_parameter_inline.yaml │ ├── produces/ │ │ ├── produces_inline.yaml │ │ ├── produces_no_content_type.yaml │ │ ├── produces_response_in_components.yaml │ │ └── produces_with_default.yaml │ └── query_parameter/ │ ├── common_query_parameter_in_components.yaml │ ├── common_query_parameter_inline.yaml │ ├── query_parameter_in_components.yaml │ └── query_parameter_inline.yaml ├── raml/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── raml/ │ │ │ ├── RamlConverter.kt │ │ │ └── extensions/ │ │ │ ├── MethodExtensions.kt │ │ │ └── ResourceExtensions.kt │ │ └── resources/ │ │ └── .gitemptydir │ └── test/ │ ├── kotlin/ │ │ └── de/ │ │ └── codecentric/ │ │ └── hikaku/ │ │ └── converters/ │ │ └── raml/ │ │ ├── RamlConverterConsumesTest.kt │ │ ├── RamlConverterDeprecationTest.kt │ │ ├── RamlConverterHeaderParameterTest.kt │ │ ├── RamlConverterHttpMethodTest.kt │ │ ├── RamlConverterInvalidInputTest.kt │ │ ├── RamlConverterPathParameterTest.kt │ │ ├── RamlConverterPathTest.kt │ │ ├── RamlConverterProducesTest.kt │ │ └── RamlConverterQueryParameterTest.kt │ └── resources/ │ ├── consumes/ │ │ ├── method_declaration_overwrites_default.raml │ │ ├── multiple_default_media_types.raml │ │ ├── multiple_method_declarations.raml │ │ ├── no_media_type.raml │ │ ├── single_default_media_type.raml │ │ └── single_method_declaration.raml │ ├── deprecation/ │ │ ├── none.raml │ │ ├── on_method.raml │ │ └── on_resource.raml │ ├── header_parameter.raml │ ├── http_method/ │ │ ├── http_methods.raml │ │ └── path_without_http_method.raml │ ├── invalid_input/ │ │ ├── different_extension.css │ │ ├── empty_file.raml │ │ ├── invalid_raml_version.raml │ │ ├── syntax_error.raml │ │ └── whitespaces_only_file.raml │ ├── path/ │ │ ├── nested_path.raml │ │ ├── nested_path_single_entry.raml │ │ └── simple_path.raml │ ├── path_parameter/ │ │ ├── nested_path_parameter.raml │ │ └── simple_path_parameter.raml │ ├── produces/ │ │ ├── method_declaration_overwrites_default.raml │ │ ├── multiple_default_media_types.raml │ │ ├── multiple_method_declarations.raml │ │ ├── no_media_type.raml │ │ ├── single_default_media_type.raml │ │ └── single_method_declaration.raml │ └── query_parameter/ │ └── query_parameter.raml ├── settings.gradle ├── spring/ │ ├── README.md │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── de/ │ │ │ └── codecentric/ │ │ │ └── hikaku/ │ │ │ └── converters/ │ │ │ └── spring/ │ │ │ ├── SpringConverter.kt │ │ │ └── extensions/ │ │ │ ├── ConsumesExtension.kt │ │ │ ├── DeprecationExtension.kt │ │ │ ├── HeaderParametersSpringExtension.kt │ │ │ ├── HttpMethodsSpringExtension.kt │ │ │ ├── MatrixParametersSpringExtension.kt │ │ │ ├── PathParametersSpringExtension.kt │ │ │ ├── PathsSpringExtension.kt │ │ │ ├── ProducesSpringExtension.kt │ │ │ └── QueryParametersSpringExtension.kt │ │ └── resources/ │ │ └── .gitemptydir │ └── test/ │ ├── kotlin/ │ │ └── de/ │ │ └── codecentric/ │ │ └── hikaku/ │ │ └── converters/ │ │ └── spring/ │ │ ├── consumes/ │ │ │ ├── ConsumesTestController.kt │ │ │ └── SpringConverterConsumesTest.kt │ │ ├── deprecation/ │ │ │ ├── DeprecationTestController.kt │ │ │ └── SpringConverterDeprecationTest.kt │ │ ├── headerparameters/ │ │ │ ├── HeaderParameterTestController.kt │ │ │ └── SpringConverterHeaderParameterTest.kt │ │ ├── httpmethod/ │ │ │ ├── HttpMethodTestController.kt │ │ │ └── SpringConverterHttpMethodTest.kt │ │ ├── matrixparameters/ │ │ │ ├── MatrixParameterTestController.kt │ │ │ └── SpringConverterMatrixParameterTest.kt │ │ ├── path/ │ │ │ ├── PathTestController.kt │ │ │ └── SpringConverterPathTest.kt │ │ ├── pathparameters/ │ │ │ ├── PathParameterTestController.kt │ │ │ └── SpringConverterPathParameterTest.kt │ │ ├── produces/ │ │ │ ├── redirect/ │ │ │ │ ├── RedirectTestController.kt │ │ │ │ └── SpringControllerRedirectTest.kt │ │ │ ├── responsebody/ │ │ │ │ ├── ProducesResponseBodyAnnotationTestController.kt │ │ │ │ └── SpringConverterProducesResponseBodyAnnotationTest.kt │ │ │ ├── restcontroller/ │ │ │ │ ├── ProducesRestControllerAnnotationTestController.kt │ │ │ │ └── SpringConverterProducesRestControllerAnnotationTest.kt │ │ │ └── servletresponse/ │ │ │ ├── ProducesServletResponseTestController.kt │ │ │ └── SpringConverterProducesServletResponseTest.kt │ │ └── queryparameters/ │ │ ├── QueryParameterTestController.kt │ │ └── SpringConverterQueryParameterTest.kt │ └── resources/ │ └── .gitemptydir └── wadl/ ├── README.md ├── build.gradle └── src/ ├── main/ │ ├── kotlin/ │ │ └── de/ │ │ └── codecentric/ │ │ └── hikaku/ │ │ └── converters/ │ │ └── wadl/ │ │ ├── WadlConverter.kt │ │ └── extensions/ │ │ └── NodeExtensions.kt │ └── resources/ │ └── .gitemptydir └── test/ ├── kotlin/ │ └── de/ │ └── codecentric/ │ └── hikaku/ │ └── converters/ │ └── wadl/ │ ├── WadlConverterConsumesTest.kt │ ├── WadlConverterEndpointTest.kt │ ├── WadlConverterHeaderParameterTest.kt │ ├── WadlConverterInvalidInputTest.kt │ ├── WadlConverterMatrixParameterTest.kt │ ├── WadlConverterPathParameterTest.kt │ ├── WadlConverterProducesTest.kt │ └── WadlConverterQueryParameterTest.kt └── resources/ ├── consumes/ │ ├── consumes_media_types_not_taken_from_produces.wadl │ ├── consumes_no_media_types.wadl │ └── consumes_three_media_types.wadl ├── endpoints/ │ ├── endpoints.wadl │ ├── endpoints_two_different_paths.wadl │ └── endpoints_two_nested_paths.wadl ├── header_parameters.wadl ├── invalid_input/ │ ├── empty_file.wadl │ ├── syntax_error.wadl │ └── whitespaces_only_file.wadl ├── matrix_parameters.wadl ├── path_parameters.wadl ├── produces/ │ ├── produces_media_types_not_taken_from_consumes.wadl │ ├── produces_no_media_types.wadl │ └── produces_three_media_types.wadl └── query_parameters.wadl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve hikaku title: '' labels: Bug assignees: '' --- # Setup information **hikaku version:** **specification converter:** **implementation converter:** **build tool and version:** `e.g. gradle 5.2.1, maven 3.5.3, ...` **test framework:** `e.g. junit 4.12, junit 5.4.0, testng 6.14.3...` # Describe the bug A clear and concise description of what the bug is. # Expected behavior A clear and concise description of what you expected to happen. # Code samples Can you provide specification snippets or code snippets? ``` CODE HERE ``` ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for hikaku title: '' labels: Feature Request assignees: '' --- **Is your feature request related to one or multiple existing converters?** If related to converters, please list affected converters here. **Describe the solution you'd like** A clear and concise description of what you want to happen. **Additional context** Add any other context, examples, snippets, links. ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: Got a question about hikaku for us? title: '' labels: Question assignees: '' --- ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gradle" directory: "/" schedule: interval: "daily" ================================================ FILE: .gitignore ================================================ # Custom **/out/ # Created by https://www.gitignore.io/api/linux,macos,gradle,kotlin,windows,intellij,intellij+iml,intellij+all ### Intellij ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: .idea/**/workspace.xml .idea/**/tasks.xml .idea/dictionaries # Sensitive or high-churn files: .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.xml .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml # Gradle: .idea/**/gradle.xml .idea/**/libraries # CMake cmake-build-debug/ # Mongo Explorer plugin: .idea/**/mongoSettings.xml ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Ruby plugin and RubyMine /.rakeTasks # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### Intellij Patch ### # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # *.iml # modules.xml # .idea/misc.xml # *.ipr # Sonarlint plugin .idea/sonarlint ### Intellij+all ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: # Sensitive or high-churn files: # Gradle: # CMake # Mongo Explorer plugin: ## File-based project format: ## Plugin-specific files: # IntelliJ # mpeltonen/sbt-idea plugin # JIRA plugin # Cursive Clojure plugin # Ruby plugin and RubyMine # Crashlytics plugin (for Android Studio and IntelliJ) ### Intellij+all Patch ### # Ignores the whole .idea folder and all .iml files # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 .idea/ # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 *.iml modules.xml .idea/misc.xml *.ipr ### Intellij+iml ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff: # Sensitive or high-churn files: # Gradle: # CMake # Mongo Explorer plugin: ## File-based project format: ## Plugin-specific files: # IntelliJ # mpeltonen/sbt-idea plugin # JIRA plugin # Cursive Clojure plugin # Ruby plugin and RubyMine # Crashlytics plugin (for Android Studio and IntelliJ) ### Intellij+iml Patch ### # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 ### Kotlin ### # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk ### Gradle ### .gradle **/build/ # Ignore Gradle GUI config gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar # Cache of project .gradletasknamecache # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # gradle/wrapper/gradle-wrapper.properties # End of https://www.gitignore.io/api/linux,macos,gradle,kotlin,windows,intellij,intellij+iml,intellij+all ================================================ FILE: .travis.yml ================================================ language: bash matrix: include: - env: JDK="openjdk@1.11.0-2" os: linux dist: trusty - env: JDK="openjdk@1.11.0-2" os: windows - env: JDK="openjdk@1.11.0-2" os: osx before_install: - source install-jdk.sh install: true script: - ./gradlew clean test ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # hikaku [![Build Status](https://api.travis-ci.org/codecentric/hikaku.svg?branch=master)](https://travis-ci.org/codecentric/hikaku) [![Maven Central Version](https://img.shields.io/maven-central/v/de.codecentric.hikaku/hikaku-core.svg)](https://search.maven.org/search?q=g:de.codecentric.hikaku)

Hikaku (比較) is japanese and means "comparison". This library tests if a REST-API implementation meets its specification. If you create your REST-API contract-first without using any type of generation, you have to make sure that specification and implementation don't diverge. The aim of this project is to meet this need and offer a mechanism to check specification and implementation for equality without having to create requests which are fired against a mock server. So this library won't check the behavior of the API, but the structural correctness. Please see also the section [limitations](#limitations) ## Currently supported + **Specifications** + [OpenAPI 3.0.X](openapi/README.md) + [RAML 1.X](raml/README.md) + [WADL](wadl/README.md) + **Implementations** + [Spring MVC 5.3.X](spring/README.md) + [Micronaut](micronaut/README.md) + [JAX-RS 3.0.X](jax-rs/README.md) + [Apache CXF](http://cxf.apache.org) + [Dropwizard](https://www.dropwizard.io) + [Jersey](https://jersey.github.io) + [Resteasy](https://resteasy.github.io) + [Restlet](https://restlet.com/open-source/documentation/user-guide/2.3/extensions/jaxrs) + [Quarkus](https://quarkus.io) Please refer to the list of [all features](docs/features.md). To check the feature support for each converter. It is possible that not every converter supports every feature. Only the intersection of the features of two `EndpointConverter`s is used for the matching. Please keep that in mind regarding the equality of implementation and specification. ## Usage Setting up a test with hikaku is very simple. You just instantiate the `Hikaku` class and provide an `EndpointConverter` for the specification and another one for the implementation. Optionally, you can also pass an instance of `HikakuConfig`. Check the list of options and default values of the [config](docs/config.md). Then you call `match()` on the `Hikaku` class. The match result is sent to one or multiple `Reporter`. If the test fails kotlin's `DefaultAsserter.fail()` method is called. ### Example There is an artifact for each converter. So we need one dependency for the specification and one for the implementation. In this example our project consists of an OpenAPI specification and a Spring implementation. The specification does not contain the _/error_ endpoints created by spring, so we want to omit those. First add the dependencies for the converters, that we want to use. In this case `hikaku-openapi` and `hikaku-spring`. ```gradle dependencies { testImplementation "de.codecentric.hikaku:hikaku-openapi:$hikakuVersion" testImplementation "de.codecentric.hikaku:hikaku-spring:$hikakuVersion" } ``` #### Kotlin And now we can create the test case: ```kotlin @SpringBootTest class SpecificationTest { @Autowired private lateinit var springContext: ApplicationContext @Test fun `specification matches implementation`() { Hikaku( specification = OpenApiConverter(Paths.get("openapi.yaml")), implementation = SpringConverter(springContext), config = HikakuConfig( filters = listOf(SpringConverter.IGNORE_ERROR_ENDPOINT) ) ) .match() } } ``` #### Java Same example in Java: ```java @SpringBootTest public class SpecificationTest { @Autowired private ApplicationContext springContext; @Test public void specification_matches_implementation() { List> filters = new ArrayList<>(); filters.add(SpringConverter.IGNORE_ERROR_ENDPOINT); List reporters = new ArrayList<>(); reporters.add(new CommandLineReporter()); new Hikaku( new OpenApiConverter(Paths.get("openapi.json")), new SpringConverter(springContext), new HikakuConfig( reporters, filters ) ) .match(); } } ``` ## Limitations Hikaku checks the implementation with static code analysis. So everything that is highly dynamic is not covered by hikaku. There might be other libraries and frameworks that can cover these aspects by checking the behavior. ### http status codes For implementations the status codes are very dynamic. There are various ways to set a http status. For example using a `ResponseEntity` object in spring or using additional filters and so on. That's why hikaku does not support http status codes. ### Request and response object For implementations both request and response objects are highly dynamic. For response objects there might be a generic `ResponseEntity` as well or interfaces with different implementations can be used. In both cases (request and response) the objects can be altered by a serialization library and there a lot of different libs out there. That's why hikaku neither supports request nor response objects. ## More Info * **Blog (english):** [Spotting mismatches between your spec and your REST-API with hikaku](https://blog.codecentric.de/en/2019/03/spot-mismatches-between-your-spec-and-your-rest-api/) * **Blog (german):** [ Abweichungen zwischen Spezifikation und REST-API mit hikaku erkennen](https://blog.codecentric.de/2019/03/abweichungen-zwischen-rest-api-spezifikation-erkennen/) * **Sample project** [A complete sample project](https://github.com/cc-jhr/hikaku-sample) ================================================ FILE: build.gradle ================================================ buildscript { ext { kotlinVersion = '1.5.21' jvmVersion = '1.8' } repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } plugins { id 'com.github.ben-manes.versions' version '0.39.0' } subprojects { apply plugin: 'idea' apply plugin: 'maven' apply plugin: 'signing' apply plugin: 'kotlin' apply plugin: 'java-library' version = '3.2.1-SNAPSHOT' sourceCompatibility = jvmVersion targetCompatibility = jvmVersion compileKotlin { kotlinOptions { freeCompilerArgs = ['-Xjsr305=strict'] jvmTarget = jvmVersion } } compileTestKotlin { kotlinOptions { freeCompilerArgs = ['-Xjsr305=strict'] jvmTarget = jvmVersion } } repositories { mavenCentral() } dependencies { api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" api "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" api "org.jetbrains.kotlin:kotlin-test:$kotlinVersion" testImplementation 'org.junit.platform:junit-platform-launcher:1.7.2' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.2' testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" testImplementation 'org.assertj:assertj-core:3.20.2' } test { useJUnitPlatform() } task javadocJar(type: Jar) { archiveClassifier = 'javadoc' from javadoc } task sourcesJar(type: Jar) { archiveClassifier = 'sources' from sourceSets.main.allSource } artifacts { archives javadocJar, sourcesJar } signing { sign configurations.archives } uploadArchives { repositories { mavenDeployer { beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { authentication(userName: findProperty('ossrhUsername'), password: findProperty('ossrhPassword')) } snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { authentication(userName: findProperty('ossrhUsername'), password: findProperty('ossrhPassword')) } pom.project { packaging = 'jar' url ='https://github.com/codecentric/hikaku' scm { connection = 'scm:git:git://github.com/codecentric/hikaku.git' developerConnection = 'scm:git:ssh://git@github.com:codecentric/hikaku.git' url = 'https://github.com/codecentric/hikaku' } licenses { license { name = 'Apache License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0' } } developers { developer { id = 'cc-jhr' name = 'Jannes Heinrich' email = '34243889+cc-jhr@users.noreply.github.com' } developer { id = 'lmller' name = 'Lovis Möller' email = 'lovis.moeller@codecentric.de' } } } } } } } ================================================ FILE: core/build.gradle ================================================ group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-core' uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-core' description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains the core elements which can be used to create additional converters and reporters.' } } } } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/Hikaku.kt ================================================ package de.codecentric.hikaku import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.EndpointConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.reporters.MatchResult import de.codecentric.hikaku.reporters.Reporter import kotlin.test.fail /** * Entry point for writing a hikaku test. Provide the [EndpointConverter]s and call [match] to test if the specification and the implementation of your REST-API match. * @param specification An [EndpointConverter] which converts your specification for the equality check. * @param implementation An [EndpointConverter] which converts your implementation for the equality check. * @param config The configuration is optional. It lets you control the matching. */ class Hikaku( private val specification: EndpointConverter, private val implementation: EndpointConverter, var config: HikakuConfig = HikakuConfig() ) { private val supportedFeatures = SupportedFeatures(specification.supportedFeatures.intersect(implementation.supportedFeatures)) private fun Set.applyConfig(config: HikakuConfig): List { val result = this.toMutableList() config.filters.forEach { result.removeAll(this.filter(it)) } return result } private fun reportResult(matchResult: MatchResult) { config.reporters.forEach { it.report(matchResult) } } /** * Calling this method creates a [MatchResult]. It will be passed to the [Reporter] defined in the configuration and call [assert] with the end result. */ fun match() { val specificationEndpoints = specification .conversionResult .applyConfig(config) .toSet() val implementationEndpoints = implementation .conversionResult .applyConfig(config) .toSet() val notExpected = implementationEndpoints.toMutableSet() val notFound = specificationEndpoints.toMutableSet() specificationEndpoints.forEach { currentEndpoint -> if (iterableContains(notExpected, currentEndpoint)) { notExpected.removeIf(endpointMatches(currentEndpoint)) notFound.removeIf(endpointMatches(currentEndpoint)) } } reportResult( MatchResult( supportedFeatures, specificationEndpoints, implementationEndpoints, notFound, notExpected ) ) if (notExpected.isNotEmpty() || notFound.isNotEmpty()) { fail("Implementation does not match specification.") } } private fun endpointMatches(otherEndpoint: Endpoint): (Endpoint) -> Boolean { return { var matches = true matches = matches && it.path == otherEndpoint.path matches = matches && it.httpMethod == otherEndpoint.httpMethod supportedFeatures.forEach { feature -> matches = when (feature) { Feature.QueryParameters -> matches && it.queryParameters == otherEndpoint.queryParameters Feature.PathParameters -> matches && it.pathParameters == otherEndpoint.pathParameters Feature.HeaderParameters -> matches && it.headerParameters == otherEndpoint.headerParameters Feature.MatrixParameters -> matches && it.matrixParameters == otherEndpoint.matrixParameters Feature.Produces -> matches && it.produces == otherEndpoint.produces Feature.Consumes -> matches && it.consumes == otherEndpoint.consumes Feature.Deprecation -> matches && it.deprecated == otherEndpoint.deprecated } } matches } } private fun iterableContains(notExpected: Set, value: Endpoint) = notExpected.any(endpointMatches(value)) } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/HikakuConfig.kt ================================================ package de.codecentric.hikaku import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.reporters.CommandLineReporter import de.codecentric.hikaku.reporters.MatchResult import de.codecentric.hikaku.reporters.Reporter /** * Configuration for [Hikaku] class. It lets you partially control the matching process. * @param reporters The [MatchResult] will be passed to one or many [Reporter] before the test either fails or succeeds. Default is a [CommandLineReporter] that prints the results to [System.out]. * @param filters Filtering rule: [Endpoint]s matching the predicate will be ignored. */ data class HikakuConfig @JvmOverloads constructor( val reporters: List = listOf(CommandLineReporter()), val filters: List<(Endpoint) -> Boolean> = emptyList() ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/SupportedFeatures.kt ================================================ package de.codecentric.hikaku import de.codecentric.hikaku.converters.EndpointConverter /** * A list of features supported by an [EndpointConverter]. */ class SupportedFeatures( private val supportedFeatures: Set = emptySet() ) : Set by supportedFeatures { constructor(vararg feature: Feature): this(feature.toSet()) enum class Feature { /** Checks the equality of query parameters. */ QueryParameters, /** Checks the equality of path parameters. */ PathParameters, /** Checks the equality of header parameters. */ HeaderParameters, /** Checks supported media type of responses. */ Produces, /** Checks supported media type of requests. */ Consumes, /** Checks the equality of matrix parameters. */ MatrixParameters, /** Checks the equality of deprecation. */ Deprecation } } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/converters/AbstractEndpointConverter.kt ================================================ package de.codecentric.hikaku.converters import de.codecentric.hikaku.endpoints.Endpoint /** * Abstract [EndpointConverter] which triggers conversion when accessing the [conversionResult]s. */ abstract class AbstractEndpointConverter : EndpointConverter { override val conversionResult: Set by lazy { this.convert() } abstract fun convert(): Set } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/converters/ClassLocator.kt ================================================ package de.codecentric.hikaku.converters import de.codecentric.hikaku.extensions.extension import de.codecentric.hikaku.extensions.nameWithoutExtension import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths /** * Original code snippet found at [dzone](https://dzone.com/articles/get-all-classes-within-package) posted by [Victor Tatai](https://dzone.com/users/74061/vtatai.html). */ object ClassLocator { fun getClasses(packageName: String): List> { val classLoader = Thread.currentThread().contextClassLoader val path = packageName.replace('.', '/') val resources = classLoader.getResources(path) val dirs = mutableListOf() resources.iterator().forEach { dirs.add(Paths.get(it.toURI())) } val classes = mutableListOf>() for (directory in dirs) { classes.addAll(findClasses(directory, packageName)) } return classes } private fun findClasses(directory: Path, packageName: String): List> { val classes = mutableListOf>() if (!Files.exists(directory)) { return classes } Files.list(directory) .forEach { if (Files.isDirectory(it)) { classes.addAll(findClasses(it, "$packageName.${it.fileName}")) } else if (it.extension() == "class") { classes.add(Class.forName("$packageName.${it.nameWithoutExtension()}")) } } return classes } } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/converters/EndpointConverter.kt ================================================ package de.codecentric.hikaku.converters import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.endpoints.Endpoint /** * Converts either a specific type of specification or implementation into the internal hikaku format in order to be able to perform a matching on the extracted components. */ interface EndpointConverter { /** Result of the conversion containing all extracted [Endpoint]s. */ val conversionResult: Set /** List of [Feature]s that this [EndpointConverter]s supports. */ val supportedFeatures: SupportedFeatures } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/converters/EndpointConverterException.kt ================================================ package de.codecentric.hikaku.converters /** * Is thrown in case an [EndpointConverter] is not able to perform the conversion. */ class EndpointConverterException(message: String? = null, cause: Throwable? = null) : Throwable(message, cause) { constructor(throwable: Throwable): this(throwable.message, throwable) } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/endpoints/Endpoint.kt ================================================ package de.codecentric.hikaku.endpoints /** * A single [Endpoint] containing all information. Each [Endpoint] consists of exactly one path in combination with exactly one [HttpMethod]. * If a REST endpoint supports multiple [HttpMethod]s, this will result in multiple [Endpoint] instances. * @param path The path excluding a base path. **Example:** `/todos` * @param produces Supported media types for the response. * @param consumes Supported media types for the request. */ data class Endpoint( val path: String, val httpMethod: HttpMethod, val queryParameters: Set = emptySet(), val pathParameters: Set = emptySet(), val headerParameters: Set = emptySet(), val matrixParameters: Set = emptySet(), val produces: Set = emptySet(), val consumes: Set = emptySet(), val deprecated: Boolean = false ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/endpoints/HeaderParameter.kt ================================================ package de.codecentric.hikaku.endpoints data class HeaderParameter( val parameterName: String, val required: Boolean = false ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/endpoints/HttpMethod.kt ================================================ package de.codecentric.hikaku.endpoints enum class HttpMethod { GET, POST, HEAD, PUT, PATCH, DELETE, TRACE, OPTIONS } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/endpoints/MatrixParameter.kt ================================================ package de.codecentric.hikaku.endpoints data class MatrixParameter( val parameterName: String, val required: Boolean = false ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/endpoints/PathParameter.kt ================================================ package de.codecentric.hikaku.endpoints data class PathParameter( val parameterName: String ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/endpoints/QueryParameter.kt ================================================ package de.codecentric.hikaku.endpoints data class QueryParameter( val parameterName: String, val required: Boolean = false ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/extensions/ClassExtensions.kt ================================================ package de.codecentric.hikaku.extensions import kotlin.reflect.KClass import kotlin.reflect.jvm.jvmName fun KClass<*>.isUnit(): Boolean { return this.isInstance(Unit) || this.jvmName == "java.lang.Void" || this.jvmName == "void" } fun KClass<*>.isString(): Boolean { return this.isInstance(String) || this.jvmName == "java.lang.String" } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/extensions/FileExtensions.kt ================================================ package de.codecentric.hikaku.extensions import java.io.File fun File.checkFileValidity(vararg extensions: String) { this.toPath().checkFileValidity(*extensions) } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/extensions/PathExtensions.kt ================================================ package de.codecentric.hikaku.extensions import java.nio.file.Files import java.nio.file.Path fun Path.nameWithoutExtension() = fileName.toString().substringBeforeLast(".") fun Path.extension() = fileName.toString().substringAfterLast(".") fun Path.checkFileValidity(vararg extensions: String) { require(Files.exists(this)) { "Given file does not exist." } require(Files.isRegularFile(this)) { "Given file is not a regular file." } if (extensions.isNotEmpty()) { extensions.map {it.substringAfter('.') } .filter { this.extension() == it } .ifEmpty { throw IllegalArgumentException("Given file is not of type ${extensions.joinToString()}") } } } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/reporters/CommandLineReporter.kt ================================================ package de.codecentric.hikaku.reporters import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.* import de.codecentric.hikaku.endpoints.* /** * Simply prints the result to [System.out]. */ class CommandLineReporter : Reporter { override fun report(endpointMatchResult: MatchResult) { val heading = "hikaku test result:" println("\n") println(heading) println("#".repeat(heading.length)) val features = endpointMatchResult.supportedFeatures.joinToString(separator = ", ") println("The following features were used for matching: HttpMethod, Path, $features") if (endpointMatchResult.notFound.isEmpty() && endpointMatchResult.notExpected.isEmpty()) { println ("") println ("✅ Test successful. Specification and implementation match.") } if (endpointMatchResult.notFound.isNotEmpty()) { println("\n👀 Expected, but unable to find:") endpointMatchResult.notFound.forEach { printEndpoint(endpointMatchResult.supportedFeatures, it) } } if (endpointMatchResult.notExpected.isNotEmpty()) { println("\n👻 Unexpected, but found:") endpointMatchResult.notExpected.forEach { printEndpoint(endpointMatchResult.supportedFeatures, it) } } } private fun printEndpoint(supportedFeatures: SupportedFeatures, endpoint: Endpoint) { var path = "< ${endpoint.httpMethod} ${endpoint.path}" supportedFeatures.forEach { path += when(it) { Feature.QueryParameters -> listQueryParameters(endpoint.queryParameters) Feature.PathParameters -> listPathParameters(endpoint.pathParameters) Feature.HeaderParameters -> listHeaderParameter(endpoint.headerParameters) Feature.MatrixParameters -> listMatrixParameter(endpoint.matrixParameters) Feature.Consumes -> listRequestMediaTypes(endpoint.consumes) Feature.Produces -> listResponseMediaTypes(endpoint.produces) Feature.Deprecation -> if (endpoint.deprecated) " Deprecated" else "" } } println("$path >") } private fun listQueryParameters(queryParameters: Set) = " QueryParameters[${queryParameters.joinToString { "${it.parameterName} (${if(it.required) "required" else "optional"})" }}]" private fun listPathParameters(pathParameters: Set) = " PathParameters[${pathParameters.joinToString { it.parameterName }}]" private fun listHeaderParameter(headerParameters: Set) = " HeaderParameters[${headerParameters.joinToString { "${it.parameterName} (${if(it.required) "required" else "optional"})" }}]" private fun listMatrixParameter(matrixParameters: Set) = " MatrixParameters[${matrixParameters.joinToString { "${it.parameterName} (${if(it.required) "required" else "optional"})" }}]" private fun listRequestMediaTypes(requestMediaTypes: Set) = " Consumes[${requestMediaTypes.joinToString()}]" private fun listResponseMediaTypes(responseMediaTypes: Set) = " Produces[${responseMediaTypes.joinToString()}]" } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/reporters/MatchResult.kt ================================================ package de.codecentric.hikaku.reporters import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.endpoints.Endpoint /** * Contains the complete result. * @param supportedFeatures Contains all features which have been used for the match. * @param specificationEndpoints All [Endpoint]s extracted from the specification. * @param implementationEndpoints All [Endpoint]s extracted from the implementation. * @param notFound A [Set] of [Endpoint]s which were expected due to their existence in the specification, but which couldn't be found. * @param notExpected A [Set] of [Endpoint]s which have been found in the implementation, but which were unexpected, because they don't exist in the specification. */ data class MatchResult( val supportedFeatures: SupportedFeatures, val specificationEndpoints: Set, val implementationEndpoints: Set, val notFound: Set, val notExpected: Set ) ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/reporters/NoOperationReporter.kt ================================================ package de.codecentric.hikaku.reporters /** * Receives the result and does nothing. */ class NoOperationReporter : Reporter { override fun report(endpointMatchResult: MatchResult) { } } ================================================ FILE: core/src/main/kotlin/de/codecentric/hikaku/reporters/Reporter.kt ================================================ package de.codecentric.hikaku.reporters /** * A [Reporter] will receive the [MatchResult] before the test terminates. */ interface Reporter { fun report(endpointMatchResult: MatchResult) } ================================================ FILE: core/src/main/resources/.gitemptydir ================================================ ================================================ FILE: core/src/test/kotlin/de/codecentric/hikaku/HikakuTest.kt ================================================ package de.codecentric.hikaku import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.EndpointConverter import de.codecentric.hikaku.endpoints.* import de.codecentric.hikaku.endpoints.HttpMethod.* import de.codecentric.hikaku.reporters.MatchResult import de.codecentric.hikaku.reporters.NoOperationReporter import de.codecentric.hikaku.reporters.Reporter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.opentest4j.AssertionFailedError import kotlin.test.assertFailsWith class HikakuTest { @Nested inner class EndpointBasicsTests { @Test fun `specification and implementation having different amounts of endpoints in conversion results let the test fail`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD) ) override val supportedFeatures = SupportedFeatures() } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } @Test fun `paths in random order match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/c", GET), Endpoint("/a", GET), Endpoint("/b", GET) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/b", GET), Endpoint("/c", GET), Endpoint("/a", GET) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `same number of Endpoints, but paths don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/c", GET), Endpoint("/a", GET), Endpoint("/b", GET) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/y", GET), Endpoint("/z", GET), Endpoint("/a", GET) ) override val supportedFeatures = SupportedFeatures() } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } @Test fun `http methods in random order match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", DELETE) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `same number of Endpoints, but http methods don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", DELETE), Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", HEAD) ) override val supportedFeatures = SupportedFeatures() } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class FeatureTests { @Nested inner class PathParameterTests { @Test fun `path parameter in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/{organizationId}/{accountId}", httpMethod = GET, pathParameters = setOf( PathParameter("accountId"), PathParameter("organizationId") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/{organizationId}/{accountId}", httpMethod = GET, pathParameters = setOf( PathParameter("organizationId"), PathParameter("accountId") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `path parameter are skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("othername") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `path parameter don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/{accountId}", httpMethod = GET, pathParameters = setOf( PathParameter("accountId") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.PathParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class QueryParameterNameTests { @Test fun `query parameter names in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter"), QueryParameter("tag"), QueryParameter("query") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("query"), QueryParameter("filter"), QueryParameter("tag") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `query parameter names are skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter") ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `query parameter names don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } @Test fun `query parameter required matches if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `query parameter required is skipped if option is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", true) ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", false) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `query parameter required don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", false) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.QueryParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class HeaderParameterNameTests { @Test fun `header parameter names in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("x-b3-traceid"), HeaderParameter("allow-cache") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache"), HeaderParameter("x-b3-traceid") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `header parameter names are skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache") ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("x-b3-traceid") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `header parameter names don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("cache") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } @Test fun `header parameter required matches if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `header parameter required is skipped if option is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", false) ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `header parameter required don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", false) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.HeaderParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class MatrixParameterNameTests { @Test fun `matrix parameter names in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag"), MatrixParameter("done") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("done"), MatrixParameter("tag") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `matrix parameter names are skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("done") ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `matrix parameter names don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("done") ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } @Test fun `matrix parameter required matches if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `matrix parameter required is skipped if option is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("tag", false) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `matrix parameter required don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("allow-cache", true) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("allow-cache", false) ) ) ) override val supportedFeatures = SupportedFeatures(Feature.MatrixParameters) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class ProducesTests { @Test fun `media types in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "text/plain" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Produces) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain", "application/json" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Produces) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `produces is skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/xml", "text/plain" ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Produces) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `media types don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/xml", "text/plain" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Produces) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Produces) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class ConsumesTests { @Test fun `media types in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "text/plain" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Consumes) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "text/plain", "application/json" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Consumes) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `produces is skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/xml", "text/plain" ) ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Consumes) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `media types don't match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/xml", "text/plain" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Consumes) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json" ) ) ) override val supportedFeatures = SupportedFeatures(Feature.Consumes) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } @Nested inner class DeprecationTests { @Test fun `deprecation info in random order match if the feature is supported by both converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = false ), Endpoint( path = "/todos/tags", httpMethod = GET, deprecated = true ) ) override val supportedFeatures = SupportedFeatures(Feature.Deprecation) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos/tags", httpMethod = GET, deprecated = true ), Endpoint( path = "/todos", httpMethod = GET, deprecated = false ) ) override val supportedFeatures = SupportedFeatures(Feature.Deprecation) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `deprecation info is skipped if the feature is not supported by one of the converters`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = false ) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = true ) ) override val supportedFeatures = SupportedFeatures(Feature.Deprecation) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `deprecation info does not match`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = false ) ) override val supportedFeatures = SupportedFeatures(Feature.Deprecation) } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = true ) ) override val supportedFeatures = SupportedFeatures(Feature.Deprecation) } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( reporters = listOf(NoOperationReporter()) ) ) //when assertFailsWith { hikaku.match() } } } } @Nested inner class ConfigTests { @Test fun `ignore endpoints with http method HEAD and OPTIONS on specification`() { //given val dummyConverterWithHeadAndOptions = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) override val supportedFeatures = SupportedFeatures() } val dummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val hikaku = Hikaku( dummyConverterWithHeadAndOptions, dummyConverter, HikakuConfig( filters = listOf ( { endpoint -> endpoint.httpMethod == HEAD }, { endpoint -> endpoint.httpMethod == OPTIONS } ), reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `ignore endpoints with http method HEAD and OPTIONS on implementation`() { //given val dummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val dummyConverterWithHeadAndOptions = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) override val supportedFeatures = SupportedFeatures() } val hikaku = Hikaku( dummyConverter, dummyConverterWithHeadAndOptions, HikakuConfig( filters = listOf ( { endpoint -> endpoint.httpMethod == HEAD }, { endpoint -> endpoint.httpMethod == OPTIONS } ), reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } @Test fun `ignore specific paths`() { //given val specificationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val implementationDummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET), Endpoint("/error", GET), Endpoint("/error", HEAD), Endpoint("/error", OPTIONS), Endpoint("/actuator/health", OPTIONS) ) override val supportedFeatures = SupportedFeatures() } val hikaku = Hikaku( specificationDummyConverter, implementationDummyConverter, HikakuConfig( filters = listOf ( { endpoint -> endpoint.path == "/error" }, { endpoint -> endpoint.path.startsWith("/actuator") } ), reporters = listOf(NoOperationReporter()) ) ) //when hikaku.match() } } @Nested inner class ReporterTests { @Test fun `MatchResult has to be passed to the Reporter`() { //given val dummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val reporter = object : Reporter { var hasBeenCalled: Boolean = false override fun report(endpointMatchResult: MatchResult) { hasBeenCalled = true } } val hikaku = Hikaku( dummyConverter, dummyConverter, HikakuConfig( reporters = listOf(reporter) ) ) //when hikaku.match() //then assertThat(reporter.hasBeenCalled).isTrue() } @Test fun `MatchResult can be passed to multiple Reporter`() { //given val dummyConverter = object : EndpointConverter { override val conversionResult: Set = setOf( Endpoint("/todos", GET) ) override val supportedFeatures = SupportedFeatures() } val firstReporter = object : Reporter { var hasBeenCalled: Boolean = false override fun report(endpointMatchResult: MatchResult) { hasBeenCalled = true } } val secondReporter = object : Reporter { var hasBeenCalled: Boolean = false override fun report(endpointMatchResult: MatchResult) { hasBeenCalled = true } } val hikaku = Hikaku( dummyConverter, dummyConverter, HikakuConfig( reporters = listOf(firstReporter, secondReporter) ) ) //when hikaku.match() //then assertThat(firstReporter.hasBeenCalled).isTrue() assertThat(secondReporter.hasBeenCalled).isTrue() } } } ================================================ FILE: core/src/test/kotlin/de/codecentric/hikaku/extensions/ClassExtensionsTest.kt ================================================ package de.codecentric.hikaku.extensions import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.lang.String as JavaString class ClassExtensionsTest { @Nested inner class IsUnitTests { @Test fun `returns true for kotlin type Unit`() { // given val obj = Unit::class // when val result = obj.isUnit() // then assertThat(result).isTrue() } @Test fun `returns true for java type Void`() { // given val obj = Void::class // when val result = obj.isUnit() // then assertThat(result).isTrue() } @Test fun `returns false for any other type`() { // given val obj = Int::class // when val result = obj.isUnit() // then assertThat(result).isFalse() } } @Nested inner class IsStringTests { @Test fun `returns true for kotlin type String`() { // given val obj = String::class // when val result = obj.isString() // then assertThat(result).isTrue() } @Test fun `returns true for java type String`() { // given val obj = JavaString::class // when val result = obj.isString() // then assertThat(result).isTrue() } @Test fun `returns false for any other type`() { // given val obj = Int::class // when val result = obj.isString() // then assertThat(result).isFalse() } } } ================================================ FILE: core/src/test/kotlin/de/codecentric/hikaku/extensions/FileExtensionsTest.kt ================================================ package de.codecentric.hikaku.extensions import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.io.File import kotlin.io.path.createTempDirectory import kotlin.test.assertFailsWith class FileExtensionsTest { @Nested inner class CheckValidityTests { @Test fun `non-existing file throws an exception`() { assertFailsWith { File("test-file-which-does-not-exist.spec").checkFileValidity() } } @Test fun `directory in validity check throws an exception`() { assertFailsWith { createTempDirectory().checkFileValidity() } } @Test fun `existing file with invalid file extension throws an exception`() { assertFailsWith { File(this::class.java.classLoader.getResource("test_file.txt").toURI()).checkFileValidity(".css") } } @Test fun `file is valid without extension check`() { //given val file = File(this::class.java.classLoader.getResource("test_file.txt").toURI()) //when file.checkFileValidity() //then //no exception is thrown } @Test fun `file is valid with extension check`() { //given val file = File(this::class.java.classLoader.getResource("test_file.txt").toURI()) //when file.checkFileValidity(".txt") //then //no exception is thrown } } } ================================================ FILE: core/src/test/kotlin/de/codecentric/hikaku/extensions/PathExtensionsTest.kt ================================================ package de.codecentric.hikaku.extensions import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.nio.file.Files.createTempDirectory import java.nio.file.Paths import kotlin.test.assertFailsWith class PathExtensionsTest { @Nested inner class CheckValidityTests { @Test fun `non-existing file throws an exception`() { assertFailsWith { Paths.get("test-file-which-does-not-exist.spec").checkFileValidity() } } @Test fun `directory in validity check throws an exception`() { assertFailsWith { createTempDirectory("tmp").checkFileValidity() } } @Test fun `existing file with invalid file extension throws an exception`() { assertFailsWith { Paths.get(this::class.java.classLoader.getResource("test_file.txt").toURI()).checkFileValidity(".css") } } @Test fun `file is valid without extension check`() { //given val file = Paths.get(this::class.java.classLoader.getResource("test_file.txt").toURI()) //when file.checkFileValidity() //then //no exception is thrown } @Test fun `file is valid with extension check`() { //given val file = Paths.get(this::class.java.classLoader.getResource("test_file.txt").toURI()) //when file.checkFileValidity(".txt") //then //no exception is thrown } } } ================================================ FILE: core/src/test/resources/test_file.txt ================================================ ================================================ FILE: docs/config.md ================================================ # hikaku - Config The following is a list if configurations. | config parameter | since | default value | description | | --- | --- | --- | --- | | filters | 3.0.0 | `emptyList()` | Endpoints matching the predicates in this list will be excluded from matching. | | reporters | 1.0.0 | `CommandLineReporter()` | A reporter receives the match results before the assertion is called. Default value is the `CommandLineReporter` which simply prints the results to command line. Another built-in option is the `NoOperationReporter` which does nothing. | ================================================ FILE: docs/features.md ================================================ # hikaku - Features The following table gives an overview of all features and which converter supports which feature. The check for endpoint paths and http methods are base functions that every converter has to support. Those are not listed in the table below. There might be various ways to declare or use a feature, so check each converter for unsupported features as well. | Feature Name | Description | [OpenApi Converter](../openapi/README.md)| [Spring Converter](../spring/README.md) | [WADL Converter](../wadl/README.md) | [RAML Converter](../raml/README.md) | [JAX-RS Converter](../jax-rs/README.md) | [Micronaut Converter](../micronaut/README.md) | | --- | --- | --- | --- | --- | --- | --- | --- | | QueryParameters | Name of a query parameter and whether the parameter is required or not. _Example:_ `/todos?filter=all`| ✅ _(1.0.0)_ | ✅ _(1.0.0)_ | ✅ _(1.1.0)_ | ✅ _(2.0.0)_ | ✅ _(2.1.0)_ | ✅ _(2.3.0)_ | | PathParameters | Name of a path parameter. _Example:_ `/todos/{id}`| ✅ _(1.0.0)_ | ✅ _(1.0.0)_ | ✅ _(1.1.0)_ | ✅ _(2.0.0)_ | ✅ _(2.1.0)_ | ✅ _(2.3.0)_ | | HeaderParameters | Name of a header parameter and whether the parameter is required or not. | ✅ _(1.1.0)_ | ✅ _(1.1.0)_ | ✅ _(1.1.0)_ | ✅ _(2.0.0)_ | ✅ _(2.1.0)_ | ✅ _(2.3.0)_ | | MatrixParameters | Name of a matrix parameter and whether the parameter is required or not. _Example:_ `/todos;param=value` | ❌ | ✅ _(2.1.0)_ | ✅ _(2.1.0)_ | ❌ | ✅ _(2.1.0)_ | ❌ | | Produces | Checks the supported media types of the response. | ✅ _(1.1.0)_ | ✅ _(1.1.0)_ | ✅ _(1.1.0)_ | ✅ _(2.0.0)_ | ✅ _(2.1.0)_ | ✅ _(2.3.0)_ | | Consumes | Checks the supported media types of the request. | ✅ _(1.1.0)_ | ✅ _(1.1.0)_ | ✅ _(1.1.0)_ | ✅ _(2.0.0)_ | ✅ _(2.1.0)_ | ✅ _(2.3.0)_ | | Deprecation | Checks deprecated endpoints are properly marked. | ✅ _(2.3.0)_ | ✅ _(2.3.0)_ | ❌ | ✅ _(2.3.0)_ | ✅ _(2.3.0)_ | ✅ _(2.3.0)_ | ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: install-jdk.sh ================================================ #!/bin/bash install_jdk () { if $jabba use $JDK; then echo $JDK was available and Jabba is using it else echo installing $JDK $jabba install "$JDK" || exit $? echo setting $JDK as Jabba default $jabba use $JDK || exit $? fi } unix_pre () { curl -sL https://github.com/shyiko/jabba/raw/master/install.sh | bash && . ~/.jabba/jabba.sh unset _JAVA_OPTIONS export jabba=jabba } linux () { unix_pre } osx () { unix_pre export JAVA_HOME="$HOME/.jabba/jdk/$JDK/Contents/Home" } windows () { PowerShell -ExecutionPolicy Bypass -Command '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-Expression (Invoke-WebRequest https://github.com/shyiko/jabba/raw/master/install.ps1 -UseBasicParsing).Content' export jabba="$HOME/.jabba/bin/jabba.exe" # Windows is unable to clean child processes, so no Gradle daemon allowed export GRADLE_OPTS="-Dorg.gradle.daemon=false $GRADLE_OPTS" echo 'export GRADLE_OPTS="-Dorg.gradle.daemon=false $GRADLE_OPTS"' >> ~/.jdk_config # Apparently exported variables are ignored in subseguent phases on Windows. Write in config file echo "export JAVA_HOME=\"${JAVA_HOME}\"" >> ~/.jdk_config echo "export PATH=\"${PATH}\"" >> ~/.jdk_config } echo "running ${TRAVIS_OS_NAME}-specific configuration" export JAVA_HOME="$HOME/.jabba/jdk/$JDK" $TRAVIS_OS_NAME export PATH="$JAVA_HOME/bin:$PATH" install_jdk which java java -version ================================================ FILE: jax-rs/README.md ================================================ # hikaku - JAX-RS Converter for JAX-RS. This converter can be used with frameworks that make use of JAX-RS. For instance: + [Apache CXF](http://cxf.apache.org) + [Dropwizard](https://www.dropwizard.io) + [Jersey](https://jersey.github.io) + [Resteasy](https://resteasy.github.io) + [Restlet](https://restlet.com/open-source/documentation/user-guide/2.3/extensions/jaxrs) + [Quarkus](https://quarkus.io) ## Feature Support Please refer to the list of [all features](../docs/features.md). To check the feature support for this converter. ## Currently not supported * Parameters defined on fields or setters ## Usage Instantiate the converter with a package name which will be scanned recursively for controllers. _Example_: `JaxRsConverter("de.codecentric.hikaku")` ================================================ FILE: jax-rs/build.gradle ================================================ group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-jax-rs' dependencies { api project(':core') api 'jakarta.ws.rs:jakarta.ws.rs-api:3.0.0' } uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-jax-rs' description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains a converter for jax-rs implementations.' } } } } ================================================ FILE: jax-rs/src/main/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverter.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.AbstractEndpointConverter import de.codecentric.hikaku.converters.ClassLocator import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.endpoints.* import de.codecentric.hikaku.endpoints.HttpMethod import de.codecentric.hikaku.extensions.isUnit import jakarta.ws.rs.* import java.lang.reflect.Method class JaxRsConverter(private val packageName: String) : AbstractEndpointConverter() { override val supportedFeatures = SupportedFeatures( Feature.QueryParameters, Feature.PathParameters, Feature.HeaderParameters, Feature.MatrixParameters, Feature.Consumes, Feature.Produces, Feature.Deprecation ) override fun convert(): Set { if (packageName.isBlank()) { throw EndpointConverterException("Package name must not be blank.") } return ClassLocator.getClasses(packageName) .filter { it.getAnnotation(Path::class.java) != null } .flatMap { extractEndpoints(it) } .toSet() } private fun extractEndpoints(resource: Class<*>): List { return resource.methods .filter { isHttpMethodAnnotationPresent(it) } .map { createEndpoint(resource, it) } } private fun isHttpMethodAnnotationPresent(method: Method): Boolean { return when { method.isAnnotationPresent(DELETE::class.java) -> true method.isAnnotationPresent(GET::class.java) -> true method.isAnnotationPresent(HEAD::class.java) -> true method.isAnnotationPresent(OPTIONS::class.java) -> true method.isAnnotationPresent(PATCH::class.java) -> true method.isAnnotationPresent(POST::class.java) -> true method.isAnnotationPresent(PUT::class.java) -> true else -> false } } private fun createEndpoint(resource: Class<*>, method: Method) = Endpoint( path = extractPath(resource, method), httpMethod = extractHttpMethod(method), pathParameters = extractPathParameters(method), queryParameters = extractQueryParameters(method), headerParameters = extractHeaderParameters(method), matrixParameters = extractMatrixParameters(method), produces = extractProduces(resource, method), consumes = extractConsumes(resource, method), deprecated = isEndpointDeprecated(method) ) private fun extractPath(resource: Class<*>, method: Method): String { var pathOnClass = resource.getAnnotation(Path::class.java).value val pathOnFunction = if (method.isAnnotationPresent(Path::class.java)) { method.getAnnotation(Path::class.java).value } else { "" } if (!pathOnClass.startsWith("/")) { pathOnClass = "/$pathOnClass" } val combinedPath = "$pathOnClass/$pathOnFunction".replace(Regex("/+"), "/") return if (combinedPath.endsWith('/')) { combinedPath.substringBeforeLast('/') } else { combinedPath } } private fun extractHttpMethod(method: Method): HttpMethod { return when { method.isAnnotationPresent(DELETE::class.java) -> HttpMethod.DELETE method.isAnnotationPresent(GET::class.java) -> HttpMethod.GET method.isAnnotationPresent(HEAD::class.java) -> HttpMethod.HEAD method.isAnnotationPresent(OPTIONS::class.java) -> HttpMethod.OPTIONS method.isAnnotationPresent(PATCH::class.java) -> HttpMethod.PATCH method.isAnnotationPresent(POST::class.java) -> HttpMethod.POST method.isAnnotationPresent(PUT::class.java) -> HttpMethod.PUT else -> throw IllegalStateException("Unable to determine http method. Valid annotation not found.") } } private fun extractProduces(resource: Class<*>, method: Method): Set { val annotationValue = when { method.isAnnotationPresent(Produces::class.java) -> method.getAnnotation(Produces::class.java).value.toSet() resource.isAnnotationPresent(Produces::class.java) -> resource.getAnnotation(Produces::class.java).value.toSet() else -> setOf("*/*") } return if (method.returnType.kotlin.isUnit()) { emptySet() } else { annotationValue } } private fun extractConsumes(resource: Class<*>, method: Method): Set { val annotationValue = when { method.isAnnotationPresent(Consumes::class.java) -> method.getAnnotation(Consumes::class.java).value.toSet() resource.isAnnotationPresent(Consumes::class.java) -> resource.getAnnotation(Consumes::class.java).value.toSet() else -> setOf("*/*") } return if (containsRequestBody(method)) { annotationValue } else { emptySet() } } private fun containsRequestBody(method: Method): Boolean { return method.parameters .filterNot { it.isAnnotationPresent(BeanParam::class.java) } .filterNot { it.isAnnotationPresent(CookieParam::class.java) } .filterNot { it.isAnnotationPresent(DefaultValue::class.java) } .filterNot { it.isAnnotationPresent(Encoded::class.java) } .filterNot { it.isAnnotationPresent(FormParam::class.java) } .filterNot { it.isAnnotationPresent(HeaderParam::class.java) } .filterNot { it.isAnnotationPresent(MatrixParam::class.java) } .filterNot { it.isAnnotationPresent(PathParam::class.java) } .filterNot { it.isAnnotationPresent(QueryParam::class.java) } .isNotEmpty() } private fun extractQueryParameters(method: Method): Set { return method.parameters .filter { it.isAnnotationPresent(QueryParam::class.java) } .map { it.getAnnotation(QueryParam::class.java) } .map { (it as QueryParam).value } .map { QueryParameter(it, false) } .toSet() } private fun extractPathParameters(method: Method): Set { return method.parameters .filter { it.isAnnotationPresent(PathParam::class.java) } .map { it.getAnnotation(PathParam::class.java) } .map { (it as PathParam).value } .map { PathParameter(it) } .toSet() } private fun extractHeaderParameters(method: Method): Set { return method.parameters .filter { it.isAnnotationPresent(HeaderParam::class.java) } .map { it.getAnnotation(HeaderParam::class.java) } .map { (it as HeaderParam).value } .map { HeaderParameter(it) } .toSet() } private fun extractMatrixParameters(method: Method): Set { return method.parameters .filter { it.isAnnotationPresent(MatrixParam::class.java) } .map { it.getAnnotation(MatrixParam::class.java) } .map { (it as MatrixParam).value } .map { MatrixParameter(it) } .toSet() } private fun isEndpointDeprecated(method: Method) = method.isAnnotationPresent(Deprecated::class.java) || method.declaringClass.isAnnotationPresent(Deprecated::class.java) } ================================================ FILE: jax-rs/src/main/resources/.gitemptydir ================================================ ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterConsumesTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterConsumesTest { @Test fun `single media type defined on class`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json" ) ) ) //when val result = JaxRsConverter("test.jaxrs.consumes.singlemediatypeonclass").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `single media type defined on function`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json" ) ) ) //when val result = JaxRsConverter("test.jaxrs.consumes.singlemediatypeonfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `single media type without request body`() { // given val specification = setOf( Endpoint( "/todos", GET) ) //when val result = JaxRsConverter("test.jaxrs.consumes.singlemediatypewithoutrequestbody").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `request body, but no annotation`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "*/*" ) ) ) //when val result = JaxRsConverter("test.jaxrs.consumes.noannotation").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `no request body, but other annotated parameter`() { // given val specification = setOf( Endpoint("/todos", GET) ) //when val result = JaxRsConverter("test.jaxrs.consumes.singlemediatypewithoutrequestbodybutotherannotatedparameter").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media type defined on class`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "application/xml" ) ) ) //when val result = JaxRsConverter("test.jaxrs.consumes.multiplemediatypesonclass").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media type defined on function`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "application/xml" ) ) ) //when val result = JaxRsConverter("test.jaxrs.consumes.multiplemediatypesonfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `function declaration overwrites class declaration`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "text/plain" ) ) ) //when val result = JaxRsConverter("test.jaxrs.consumes.functiondeclarationoverwritesclassdeclaration").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterDeprecationTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterDeprecationTest { @Test fun `no deprecation`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = false ) ) //when val result = JaxRsConverter("test.jaxrs.deprecation.none").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `deprecated class`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = true ) ) //when val result = JaxRsConverter("test.jaxrs.deprecation.onclass").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `deprecated function`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = true ) ) //when val result = JaxRsConverter("test.jaxrs.deprecation.onfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterHeaderParametersTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HeaderParameter import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterHeaderParameterTest { @Test fun `header parameter on function`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache") ) ) ) //when val result = JaxRsConverter("test.jaxrs.headerparameters.onfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterHttpMethodsTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterHttpMethodsTest { @Test fun `extract all available http methods`() { //given val specification = setOf( Endpoint("/todos", GET), Endpoint("/todos", DELETE), Endpoint("/todos", POST), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val result = JaxRsConverter("test.jaxrs.httpmethod.allmethods").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `resource class without http method annotation`() { //when val result = JaxRsConverter("test.jaxrs.httpmethod.noannotation").conversionResult //then assertThat(result).isEmpty() } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterMatrixParametersTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.MatrixParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterMatrixParametersTest { @Test fun `matrix parameter on function`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag") ) ) ) //when val result = JaxRsConverter("test.jaxrs.matrixparameters.onfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterPackageDefinitionTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.converters.EndpointConverterException import org.junit.jupiter.api.Test import kotlin.test.assertFailsWith class JaxRsConverterPackageDefinitionTest { @Test fun `invoking converter with empty string leads to EndpointConverterException`() { assertFailsWith { JaxRsConverter("").conversionResult } } @Test fun `invoking converter with blank string leads to EndpointConverterException`() { assertFailsWith { JaxRsConverter(" ").conversionResult } } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterPathParametersTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterPathParametersTest { @Test fun `no path parameter`() { //given val specification = setOf( Endpoint("/todos/{id}", GET) ) //when val result = JaxRsConverter("test.jaxrs.pathparameters.nopathparameter").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `path parameter on function`() { //given val specification = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) //when val result = JaxRsConverter("test.jaxrs.pathparameters.onfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterPathTests.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterPathTests { @Test fun `simple path`() { // given val specification = setOf( Endpoint("/todos", GET) ) //when val result = JaxRsConverter("test.jaxrs.path.simplepath").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `simple path without leading slash`() { // given val specification = setOf( Endpoint("/todos", GET) ) //when val result = JaxRsConverter("test.jaxrs.path.simplepathwithoutleadingslash").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `nested path`() { // given val specification = setOf( Endpoint("/todo/list", GET) ) //when val result = JaxRsConverter("test.jaxrs.path.nestedpath").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `nested path without leading slash`() { // given val specification = setOf( Endpoint("/todo/list", GET) ) //when val result = JaxRsConverter("test.jaxrs.path.nestedpathwithoutleadingslash").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `resource class is not detected, if there is no Path annotation on class level`() { //when val result = JaxRsConverter("test.jaxrs.path.nopathonclass").conversionResult //then assertThat(result).isEmpty() } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterProducesTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterProducesTest { @Test fun `single media type defined on class`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json" ) ) ) //when val result = JaxRsConverter("test.jaxrs.produces.singlemediatypeonclass").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `single media type defined on function`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json" ) ) ) //when val result = JaxRsConverter("test.jaxrs.produces.singlemediatypeonfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `single media type without return type`() { // given val specification = setOf( Endpoint( "/todos", GET) ) //when val result = JaxRsConverter("test.jaxrs.produces.singlemediatypewithoutreturntype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `return type, but no annotation`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "*/*" ) ) ) //when val result = JaxRsConverter("test.jaxrs.produces.noannotation").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media type defined on class`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/xml" ) ) ) //when val result = JaxRsConverter("test.jaxrs.produces.multiplemediatypesonclass").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media type defined on function`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/xml" ) ) ) //when val result = JaxRsConverter("test.jaxrs.produces.multiplemediatypesonfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `function declaration overwrites class declaration`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "text/plain" ) ) ) //when val result = JaxRsConverter("test.jaxrs.produces.functiondeclarationoverwritesclassdeclaration").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/de/codecentric/hikaku/converters/jaxrs/JaxRsConverterQueryParameterTest.kt ================================================ package de.codecentric.hikaku.converters.jaxrs import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.QueryParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class JaxRsConverterQueryParameterTest { @Test fun `query parameter on function`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter") ) ) ) //when val result = JaxRsConverter("test.jaxrs.queryparameters.onfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/functiondeclarationoverwritesclassdeclaration/FunctionDeclarationOverwritesClassDeclaration.kt ================================================ package test.jaxrs.consumes.functiondeclarationoverwritesclassdeclaration import jakarta.ws.rs.Consumes import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") @Consumes("application/xml") @Suppress("UNUSED_PARAMETER") class FunctionDeclarationOverwritesClassDeclaration { @GET @Consumes("application/json", "text/plain") fun todo(todo: Todo) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/multiplemediatypesonclass/MultipleMediaTypesOnClass.kt ================================================ package test.jaxrs.consumes.multiplemediatypesonclass import jakarta.ws.rs.Consumes import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") @Consumes("application/json", "application/xml") @Suppress("UNUSED_PARAMETER") class MultipleMediaTypesOnClass { @GET fun todo(todo: Todo) {} } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/multiplemediatypesonfunction/MultipleMediaTypesOnFunction.kt ================================================ package test.jaxrs.consumes.multiplemediatypesonfunction import jakarta.ws.rs.Consumes import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") @Suppress("UNUSED_PARAMETER") class MultipleMediaTypesOnFunction { @GET @Consumes("application/json", "application/xml") fun todo(todo: Todo) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/noannotation/NoAnnotation.kt ================================================ package test.jaxrs.consumes.noannotation import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") @Suppress("UNUSED_PARAMETER") class NoAnnotation { @GET fun todo(todo: Todo) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/singlemediatypeonclass/ProducesOnClass.kt ================================================ package test.jaxrs.consumes.singlemediatypeonclass import jakarta.ws.rs.Consumes import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") @Consumes("application/json") @Suppress("UNUSED_PARAMETER") class ProducesOnClass { @GET fun todo(todo: Todo) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/singlemediatypeonfunction/ProducesOnFunction.kt ================================================ package test.jaxrs.consumes.singlemediatypeonfunction import jakarta.ws.rs.Consumes import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") @Suppress("UNUSED_PARAMETER") class ProducesOnFunction { @GET @Consumes("application/json") fun todo(todo: Todo) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/singlemediatypewithoutrequestbody/SingleMediaTypeWithoutRequestBody.kt ================================================ package test.jaxrs.consumes.singlemediatypewithoutrequestbody import jakarta.ws.rs.Consumes import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todos") class SingleMediaTypeWithoutRequestBody { @GET @Consumes("application/json") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/consumes/singlemediatypewithoutrequestbodybutotherannotatedparameter/SingleMediaTypeWithoutRequestBodyButOtherAnnotatedParameter.kt ================================================ package test.jaxrs.consumes.singlemediatypewithoutrequestbodybutotherannotatedparameter import jakarta.ws.rs.* @Path("/todos") @Suppress("UNUSED_PARAMETER") class SingleMediaTypeWithoutRequestBodyButOtherAnnotatedParameter { @GET @Consumes("application/json") fun todo(@Encoded filter: String) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/deprecation/none/NoDeprecation.kt ================================================ package test.jaxrs.deprecation.none import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todos") class NoDeprecation { @GET fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/deprecation/onclass/DeprecationOnClass.kt ================================================ package test.jaxrs.deprecation.onclass import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todos") @Deprecated("Test") class DeprecationOnClass { @GET fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/deprecation/onfunction/DeprecationOnFunction.kt ================================================ package test.jaxrs.deprecation.onfunction import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todos") class DeprecationOnFunction { @GET @Deprecated("Test") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/headerparameters/onfunction/HeaderParametersOnFunction.kt ================================================ package test.jaxrs.headerparameters.onfunction import jakarta.ws.rs.GET import jakarta.ws.rs.HeaderParam import jakarta.ws.rs.Path @Path("/todos") @Suppress("UNUSED_PARAMETER") class HeaderParameterOnFunction { @GET fun todo(@HeaderParam("allow-cache") allowCache: String) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/httpmethod/allmethods/AllHttpMethods.kt ================================================ package test.jaxrs.httpmethod.allmethods import jakarta.ws.rs.* @Path("/todos") class AllHttpMethods { @GET fun getTodo() { } @DELETE fun deleteTodo() { } @POST fun postTodo() { } @PUT fun putTodos() { } @PATCH fun patchTodos() { } @OPTIONS fun optionsTodos() { } @HEAD fun headTodos() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/httpmethod/noannotation/NoAnnotation.kt ================================================ package test.jaxrs.httpmethod.noannotation import jakarta.ws.rs.Path @Path("") class NoAnnotation ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/matrixparameters/onfunction/MatrixParametersOnFunction.kt ================================================ package test.jaxrs.matrixparameters.onfunction import jakarta.ws.rs.GET import jakarta.ws.rs.MatrixParam import jakarta.ws.rs.Path @Path("/todos") @Suppress("UNUSED_PARAMETER") class MatrixParameterOnFunction { @GET fun todo(@MatrixParam("tag") tag: String) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/path/nestedpath/NestedPath.kt ================================================ package test.jaxrs.path.nestedpath import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todo") class NestedPath { @GET @Path("/list") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/path/nestedpathwithoutleadingslash/NestedPathWithoutLeadingSlash.kt ================================================ package test.jaxrs.path.nestedpathwithoutleadingslash import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todo") class NestedPath { @GET @Path("list") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/path/nopathonclass/NoPathAnnotationOnclass.kt ================================================ package test.jaxrs.path.nopathonclass import jakarta.ws.rs.GET import jakarta.ws.rs.Path class NoPathAnnotationOnclass { @GET @Path("/todos") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/path/simplepath/SimplePath.kt ================================================ package test.jaxrs.path.simplepath import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todos") class SimplePath { @GET fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/path/simplepathwithoutleadingslash/SimplePathWithoutLeadingSlash.kt ================================================ package test.jaxrs.path.simplepathwithoutleadingslash import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("todos") class SimplePathWithoutLeadingSlash { @GET fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/pathparameters/nopathparameter/NoPathParameter.kt ================================================ package test.jaxrs.pathparameters.nopathparameter import jakarta.ws.rs.GET import jakarta.ws.rs.Path @Path("/todos") class NoPathParameter { @GET @Path("/{id}") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/pathparameters/onfunction/PathParameterOnFunction.kt ================================================ package test.jaxrs.pathparameters.onfunction import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.PathParam @Path("/todos") @Suppress("UNUSED_PARAMETER") class PathParameterOnFunction { @GET @Path("/{id}") fun todo(@PathParam("id") id: String) { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/functiondeclarationoverwritesclassdeclaration/FunctionDeclarationOverwritesClassDeclaration.kt ================================================ package test.jaxrs.produces.functiondeclarationoverwritesclassdeclaration import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces data class Todo(val description: String = "") @Path("/todos") @Produces("application/xml") class FunctionDeclarationOverwritesClassDeclaration { @GET @Produces("application/json", "text/plain") fun todo() = Todo() } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/multiplemediatypesonclass/MultipleMediaTypesOnClass.kt ================================================ package test.jaxrs.produces.multiplemediatypesonclass import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces data class Todo(val description: String = "") @Path("/todos") @Produces("application/json", "application/xml") class MultipleMediaTypesOnClass { @GET fun todo() = Todo() } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/multiplemediatypesonfunction/MultipleMediaTypesOnFunction.kt ================================================ package test.jaxrs.produces.multiplemediatypesonfunction import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces data class Todo(val description: String = "") @Path("/todos") class MultipleMediaTypesOnFunction { @GET @Produces("application/json", "application/xml") fun todo() = Todo() } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/noannotation/NoAnnotation.kt ================================================ package test.jaxrs.produces.noannotation import jakarta.ws.rs.GET import jakarta.ws.rs.Path data class Todo(val description: String = "") @Path("/todos") class NoAnnotation { @GET fun todo() = Todo() } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/singlemediatypeonclass/ProducesOnClass.kt ================================================ package test.jaxrs.produces.singlemediatypeonclass import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces data class Todo(val description: String = "") @Path("/todos") @Produces("application/json") class ProducesOnClass { @GET fun todo() = Todo() } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/singlemediatypeonfunction/ProducesOnFunction.kt ================================================ package test.jaxrs.produces.singlemediatypeonfunction import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces data class Todo(val description: String = "") @Path("/todos") class ProducesOnFunction { @GET @Produces("application/json") fun todo() = Todo() } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/produces/singlemediatypewithoutreturntype/SingleMediaTypeWithoutReturnType.kt ================================================ package test.jaxrs.produces.singlemediatypewithoutreturntype import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces @Path("/todos") class SingleMediaTypeWithoutReturnType { @GET @Produces("application/json") fun todo() { } } ================================================ FILE: jax-rs/src/test/kotlin/test/jaxrs/queryparameters/onfunction/QueryParameterOnFunction.kt ================================================ package test.jaxrs.queryparameters.onfunction import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.QueryParam @Path("/todos") @Suppress("UNUSED_PARAMETER") class QueryParameterOnFunction { @GET fun todo(@QueryParam("filter") filter: String) { } } ================================================ FILE: jax-rs/src/test/resources/.gitemptydir ================================================ ================================================ FILE: micronaut/README.md ================================================ # hikaku - Micronaut Converter for Micronaut. ## Feature Support Please refer to the list of [all features](../docs/features.md). To check the feature support for this converter. ### Paths + Supports Controller annotation combined with empty HttpMethod annotation + _Example:_ ``` @Controller("/todos") class TodosController { @Get fun todos() { } } ``` + Supports Controller annotation combined with HttpMethod annotation + _Example:_ ``` @Controller("/todo") class TodosController { @Get("/list") fun todos() { } } ``` + Supports alias `uri` for HttpMethod annotation + _Example:_ `@Get(uri = "/todos")` + Supports paths with leading and non-leading slash on both Controller and HttpMethod annotation + _Examples:_ + `@Controller("/todos")` and `@Controller("todos")` + `@Get("/todos")` and `@Get("todos")` ### Path parameters + Supports path parameter without annotation + _Examples:_ ``` @Controller("/todos/{id}") class TodosController { @Get fun todos(id: String) { } } ``` + Supports path parameter with annotation + _Examples:_ ``` @Controller("/todos/{id}") class TodosController { @Get fun todos(@PathVariable("id") otherName: String) { } } ``` + Supports using alias `name` + _Examples:_ `@PathVariable(name = "id") otherName: String` ### Query parameters + Supports query parameter without annotation + _Examples:_ ``` @Controller("/todos") class TodosController { @Get fun todos(filter: String) { } } ``` + Supports query parameter with annotation + _Examples:_ ``` @Controller("/todos") class TodosController { @Get fun todos(@QueryValue("filter") filter: String) { } } ``` + Supports setting the parameter _required_ or _optional_ based on the existence of a default value + _Examples:_ `@QueryValue("filter", defaultValue = "all") filter: String` ### Header parameters + Supports required header parameter + _Examples:_ ``` @Controller("/todos") class TodosController { @Get fun todos(@Header("allow-cache") otherName: String) { } } ``` + Supports optional header parameter based on the existence of a default value + _Examples:_ `@Header("allow-cache", defaultValue = "true") otherName: String` ### Consumes + Supports default media type `application/json` + Supports single and multiple media type declarations in Controller annotation + Supports Consumes annotation with single and multiple media type declarations on class and function + Supports Consumes annotation overriding the value of the Controller annotation ### Produces + Supports default media type `application/json` + Supports single and multiple media type declarations in Controller annotation + Supports Produces annotation with single and multiple media type declarations on class and function + Supports Produces annotation overriding the value of the Controller annotation ## Usage Instantiate the converter with a package name which will be scanned recursively for controllers. _Example_: `MicronautConverter("de.codecentric.hikaku")` ================================================ FILE: micronaut/build.gradle ================================================ group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-micronaut' dependencies { api project(':core') api 'io.micronaut:micronaut-http:3.0.0' } uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-micronaut' description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains a converter for micronaut implementations.' } } } } ================================================ FILE: micronaut/src/main/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverter.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.AbstractEndpointConverter import de.codecentric.hikaku.converters.ClassLocator import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.endpoints.* import de.codecentric.hikaku.extensions.isUnit import io.micronaut.http.annotation.* import java.lang.reflect.Method import kotlin.reflect.full.findAnnotation import kotlin.reflect.jvm.kotlinFunction class MicronautConverter(private val packageName: String) : AbstractEndpointConverter() { override val supportedFeatures = SupportedFeatures( Feature.QueryParameters, Feature.PathParameters, Feature.HeaderParameters, Feature.Produces, Feature.Consumes, Feature.Deprecation ) override fun convert(): Set { if (packageName.isBlank()) { throw EndpointConverterException("Package name must not be blank.") } return ClassLocator.getClasses(packageName) .filter { it.getAnnotation(Controller::class.java) != null } .flatMap { extractEndpoints(it) } .toSet() } private fun extractEndpoints(resource: Class<*>): List { return resource.methods .filter { isHttpMethodAnnotationPresent(it) } .map { createEndpoint(resource, it) } } private fun isHttpMethodAnnotationPresent(method: Method): Boolean { return when { method.isAnnotationPresent(Delete::class.java) -> true method.isAnnotationPresent(Get::class.java) -> true method.isAnnotationPresent(Head::class.java) -> true method.isAnnotationPresent(Options::class.java) -> true method.isAnnotationPresent(Patch::class.java) -> true method.isAnnotationPresent(Post::class.java) -> true method.isAnnotationPresent(Put::class.java) -> true else -> false } } private fun createEndpoint(resource: Class<*>, method: Method): Endpoint { val path = extractPath(resource, method) return Endpoint( path = path, httpMethod = extractHttpMethod(method), queryParameters = extractQueryParameters(path, method), pathParameters = extractPathParameters(path, method), headerParameters = extractHeaderParameters(method), consumes = extractConsumes(resource, method), produces = extractProduces(resource, method), deprecated = isEndpointDeprecated(method) ) } private fun extractProduces(resource: Class<*>, method: Method): Set { val methodHasNoReturnType = method.returnType.kotlin.isUnit() if (methodHasNoReturnType) { return emptySet() } val mediaTypesOnFunction = method.kotlinFunction ?.annotations ?.filterIsInstance() ?.flatMap { it.value.map { entry -> entry } } ?.toSet() .orEmpty() if (mediaTypesOnFunction.isNotEmpty()) { return mediaTypesOnFunction } val mediaTypesOnControllerByConsumesAnnotation = resource.getAnnotation(Produces::class.java) ?.value ?.toSet() .orEmpty() if (mediaTypesOnControllerByConsumesAnnotation.isNotEmpty()) { return mediaTypesOnControllerByConsumesAnnotation } val mediaTypesDefinedByControllerAnnotation = resource.getAnnotation(Controller::class.java) .produces .toSet() if (mediaTypesDefinedByControllerAnnotation.isNotEmpty()) { return mediaTypesDefinedByControllerAnnotation } return setOf("application/json") } private fun extractConsumes(resource: Class<*>, method: Method): Set { val methodAwaitsPayload = method.kotlinFunction ?.parameters ?.any { it.findAnnotation() != null } ?: false if (!methodAwaitsPayload) { return emptySet() } val mediaTypesOnFunction = method.kotlinFunction ?.annotations ?.filterIsInstance() ?.flatMap { it.value.map { entry -> entry } } ?.toSet() .orEmpty() if (mediaTypesOnFunction.isNotEmpty()) { return mediaTypesOnFunction } val mediaTypesOnControllerByConsumesAnnotation = resource.getAnnotation(Consumes::class.java) ?.value ?.toSet() .orEmpty() if (mediaTypesOnControllerByConsumesAnnotation.isNotEmpty()) { return mediaTypesOnControllerByConsumesAnnotation } val mediaTypesDefinedByControllerAnnotation = resource.getAnnotation(Controller::class.java) .consumes .toSet() if (mediaTypesDefinedByControllerAnnotation.isNotEmpty()) { return mediaTypesDefinedByControllerAnnotation } return setOf("application/json") } private fun extractPath(resource: Class<*>, method: Method): String { var pathOnClass = resource.getAnnotation(Controller::class.java).value val pathOnFunction = when { method.isAnnotationPresent(Delete::class.java) -> method.getAnnotation(Delete::class.java).value method.isAnnotationPresent(Get::class.java) -> method.getAnnotation(Get::class.java).value method.isAnnotationPresent(Head::class.java) -> method.getAnnotation(Head::class.java).value method.isAnnotationPresent(Options::class.java) -> method.getAnnotation(Options::class.java).value method.isAnnotationPresent(Patch::class.java) -> method.getAnnotation(Patch::class.java).value method.isAnnotationPresent(Post::class.java) -> method.getAnnotation(Post::class.java).value method.isAnnotationPresent(Put::class.java) -> method.getAnnotation(Put::class.java).value else -> "" } if (!pathOnClass.startsWith("/")) { pathOnClass = "/$pathOnClass" } val combinedPath = "$pathOnClass/$pathOnFunction".replace(Regex("/+"), "/") return if (combinedPath.endsWith('/')) { combinedPath.substringBeforeLast('/') } else { combinedPath } } private fun extractHttpMethod(method: Method): HttpMethod { return when { method.isAnnotationPresent(Delete::class.java) -> HttpMethod.DELETE method.isAnnotationPresent(Get::class.java) -> HttpMethod.GET method.isAnnotationPresent(Head::class.java) -> HttpMethod.HEAD method.isAnnotationPresent(Options::class.java) -> HttpMethod.OPTIONS method.isAnnotationPresent(Patch::class.java) -> HttpMethod.PATCH method.isAnnotationPresent(Post::class.java) -> HttpMethod.POST method.isAnnotationPresent(Put::class.java) -> HttpMethod.PUT else -> throw IllegalStateException("Unable to determine http method. Valid annotation not found.") } } private fun extractQueryParameters(path: String, method: Method): Set { val queryParameters = method.parameters .filter { it.isAnnotationPresent(QueryValue::class.java) } .map { it.getAnnotation(QueryValue::class.java) } .map { it as QueryValue } .map { QueryParameter(it.value, it.defaultValue.isBlank()) } .toMutableSet() val queryParameterWithoutAnnotation = methodParametersWithoutAnnotation(method).filterNot { templatesInPath(path).contains(it) } .filterNotNull() .map { QueryParameter(it, false) } .toSet() queryParameters.addAll(queryParameterWithoutAnnotation) return queryParameters } private fun extractPathParameters(path: String, method: Method): Set { val parameters = method.parameters .filter { it.isAnnotationPresent(PathVariable::class.java) } .map { it.getAnnotation(PathVariable::class.java) as PathVariable } .map { val pathParameter = if (it.value.isNotBlank()) { it.value } else { it.name } PathParameter(pathParameter) } .toMutableSet() val pathParametersWithoutAnnotation = templatesInPath(path) .filter { methodParametersWithoutAnnotation(method).contains(it) } .map { PathParameter(it) } .toSet() parameters.addAll(pathParametersWithoutAnnotation) return parameters } private fun methodParametersWithoutAnnotation(method: Method) = method.kotlinFunction ?.parameters ?.filter { it.annotations.isEmpty() } ?.map { it.name } .orEmpty() private fun templatesInPath(path: String) = Regex("\\{.+\\}").findAll(path) .map { it.value } .map { it.removePrefix("{") } .map { it.removeSuffix("}") } .toSet() private fun extractHeaderParameters(method: Method): Set { return method.parameters .filter { it.isAnnotationPresent(Header::class.java) } .map { it.getAnnotation(Header::class.java) } .map { it as Header } .map { HeaderParameter(it.value, it.defaultValue.isBlank()) } .toSet() } private fun isEndpointDeprecated(method: Method) = method.isAnnotationPresent(Deprecated::class.java) || method.declaringClass.isAnnotationPresent(Deprecated::class.java) } ================================================ FILE: micronaut/src/main/resources/.gitemptydir ================================================ ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterConsumesTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.POST import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class MicronautConverterConsumesTest { @Nested inner class DeclaredByControllerOnClass { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "text/plain" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onclass.onlycontroller.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "text/plain", "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onclass.onlycontroller.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class ConsumesOnClassOverridesController { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onclass.consumesoverridescontroller.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "application/json", "application/pdf" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onclass.consumesoverridescontroller.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class DeclaredByConsumesOnFunction { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "text/plain" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onfunction.onlyconsumes.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "text/plain", "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onfunction.onlyconsumes.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class ConsumesOnFunctionOverridesController { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onfunction.consumesoverridescontroller.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "application/json", "application/pdf" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.onfunction.consumesoverridescontroller.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Test fun `use default media if no consume info has been set`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf( "application/json" ) ) ) //when val result = MicronautConverter("test.micronaut.consumes.default").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterDeprecationTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class MicronautConverterDeprecationTest { @Test fun `no deprecation`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = false ) ) //when val result = MicronautConverter("test.micronaut.deprecation.none").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `deprecated class`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = true ) ) //when val result = MicronautConverter("test.micronaut.deprecation.onclass").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `deprecated function`() { // given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, deprecated = true ) ) //when val result = MicronautConverter("test.micronaut.deprecation.onfunction").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterHeaderParameterTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HeaderParameter import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class MicronautConverterHeaderParameterTest { @Test fun `optional header parameter`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", false) ) ) ) //when val result = MicronautConverter("test.micronaut.headerparameters.optional").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `required header parameter`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) //when val result = MicronautConverter("test.micronaut.headerparameters.required").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterPackageDefinitionTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.converters.EndpointConverterException import org.junit.jupiter.api.Test import kotlin.test.assertFailsWith class MicronautConverterPackageDefinitionTest { @Test fun `invoking converter with empty string leads to EndpointConverterException`() { assertFailsWith { MicronautConverter("").conversionResult } } @Test fun `invoking converter with blank string leads to EndpointConverterException`() { assertFailsWith { MicronautConverter(" ").conversionResult } } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterPathParameterTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class MicronautConverterPathParameterTest { @Test fun `path parameter defined by variable name`() { //given val specification = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) //when val result = MicronautConverter("test.micronaut.pathparameters.variable").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `path parameter defined by annotation using 'value'`() { //given val specification = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) //when val result = MicronautConverter("test.micronaut.pathparameters.annotation.value").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `path parameter defined by annotation using 'name'`() { //given val specification = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) //when val result = MicronautConverter("test.micronaut.pathparameters.annotation.name").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterPathTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class MicronautConverterPathTest { @Nested inner class PathOnlyOnControllerTests { @Test fun `controller annotation with DELETE annotation`() { //given val specification = setOf( Endpoint("/todos", DELETE) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.delete").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with GET annotation`() { //given val specification = setOf( Endpoint("/todos", GET) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.get").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with HEAD annotation`() { //given val specification = setOf( Endpoint("/todos", HEAD) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.head").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with OPTIONS annotation`() { //given val specification = setOf( Endpoint("/todos", OPTIONS) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.options").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with PATCH annotation`() { //given val specification = setOf( Endpoint("/todos", PATCH) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.patch").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with POST annotation`() { //given val specification = setOf( Endpoint("/todos", POST) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.post").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with PUT annotation`() { //given val specification = setOf( Endpoint("/todos", PUT) ) //when val result = MicronautConverter("test.micronaut.path.onlycontrollerannotation.put").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class PathInBothControllerAndHttpMethodAnnotationTests { @Test fun `controller annotation with DELETE annotation`() { //given val specification = setOf( Endpoint("/todo/list", DELETE) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.delete").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with GET annotation`() { //given val specification = setOf( Endpoint("/todo/list", GET) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.get").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with HEAD annotation`() { //given val specification = setOf( Endpoint("/todo/list", HEAD) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.head").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with OPTIONS annotation`() { //given val specification = setOf( Endpoint("/todo/list", OPTIONS) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.options").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with PATCH annotation`() { //given val specification = setOf( Endpoint("/todo/list", PATCH) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.patch").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with POST annotation`() { //given val specification = setOf( Endpoint("/todo/list", POST) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.post").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `controller annotation with PUT annotation`() { //given val specification = setOf( Endpoint("/todo/list", PUT) ) //when val result = MicronautConverter("test.micronaut.path.combinedcontrollerandhttpmethodannotation.put").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Test fun `method does not provide any http method annotation`() { //when val result = MicronautConverter("test.micronaut.path.nohttpmethodannotation").conversionResult //then assertThat(result).isEmpty() } @Test fun `inner path segment without leading slash`() { //given val specification = setOf( Endpoint("/todo/list", GET) ) //when val result = MicronautConverter("test.micronaut.path.innerpathsegmentwithoutleadingslash").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `first path segment without leading slash`() { //given val specification = setOf( Endpoint("/todo/list", GET) ) //when val result = MicronautConverter("test.micronaut.path.firstpathsegmentwithoutleadingslash").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterProducesTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class MicronautConverterProducesTest { @Nested inner class DeclaredByControllerOnClass { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onclass.onlycontroller.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain", "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onclass.onlycontroller.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class ProducesOnClassOverridesController { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onclass.producesoverridescontroller.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/pdf" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onclass.producesoverridescontroller.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class DeclaredByProducesOnFunction { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onfunction.onlyproduces.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain", "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onfunction.onlyproduces.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class ProducesOnFunctionOverridesController { @Test fun `single media type`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/xml" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onfunction.producesoverridescontroller.singlemediatype").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple media types`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/pdf" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.onfunction.producesoverridescontroller.multiplemediatypes").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } @Test fun `use default media if no produces info has been set`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json" ) ) ) //when val result = MicronautConverter("test.micronaut.produces.default").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/de/codecentric/hikaku/converters/micronaut/MicronautConverterQueryParameterTest.kt ================================================ package de.codecentric.hikaku.converters.micronaut import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.QueryParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test class MicronautConverterQueryParameterTest { @Test fun `query parameter required`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", true) ) ) ) //when val result = MicronautConverter("test.micronaut.queryparameters.required.annotation").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `query parameter optional, because a default value exists`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", false) ) ) ) //when val result = MicronautConverter("test.micronaut.queryparameters.optional").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `query is required and defined without annotation, because no matching template exists in url`() { //given val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("filter", false) ) ) ) //when val result = MicronautConverter("test.micronaut.queryparameters.required.withoutannotation").conversionResult //then assertThat(result).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/Todo.kt ================================================ package test.micronaut data class Todo(val description: String = "") ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/default/ConsumesDefaultMediaTypeTestController.kt ================================================ package test.micronaut.consumes.default import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos") @Suppress("UNUSED_PARAMETER") class ConsumesDefaultMediaTypeTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onclass/consumesoverridescontroller/multiplemediatypes/ConsumesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.consumes.onclass.consumesoverridescontroller.multiplemediatypes import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Consumes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain", "application/xml"]) @Consumes("application/json", "application/pdf") @Suppress("UNUSED_PARAMETER") class ConsumesMultipleMediaTypesTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onclass/consumesoverridescontroller/singlemediatype/ConsumesSingleMediaTypeTestController.kt ================================================ package test.micronaut.consumes.onclass.consumesoverridescontroller.singlemediatype import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Consumes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Consumes("application/xml") @Controller("/todos", consumes = ["text/plain"]) @Suppress("UNUSED_PARAMETER") class ConsumesSingleMediaTypeTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onclass/onlycontroller/multiplemediatypes/ConsumesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.consumes.onclass.onlycontroller.multiplemediatypes import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain", "application/xml"]) @Suppress("UNUSED_PARAMETER") class ConsumesMultipleMediaTypesTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onclass/onlycontroller/singlemediatype/ConsumesSingleMediaTypeTestController.kt ================================================ package test.micronaut.consumes.onclass.onlycontroller.singlemediatype import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain"]) @Suppress("UNUSED_PARAMETER") class ConsumesSingleMediaTypeTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onfunction/consumesoverridescontroller/multiplemediatypes/ConsumesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.consumes.onfunction.consumesoverridescontroller.multiplemediatypes import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Consumes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain", "application/xml"]) @Suppress("UNUSED_PARAMETER") class ConsumesMultipleMediaTypesTestController { @Post @Consumes("application/json", "application/pdf") fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onfunction/consumesoverridescontroller/singlemediatype/ConsumesSingleMediaTypeTestController.kt ================================================ package test.micronaut.consumes.onfunction.consumesoverridescontroller.singlemediatype import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Consumes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain"]) @Suppress("UNUSED_PARAMETER") class ConsumesSingleMediaTypeTestController { @Post @Consumes("application/xml") fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onfunction/onlyconsumes/multiplemediatypes/ConsumesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.consumes.onfunction.onlyconsumes.multiplemediatypes import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain", "application/xml"]) @Suppress("UNUSED_PARAMETER") class ConsumesMultipleMediaTypesTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/consumes/onfunction/onlyconsumes/singlemediatype/ConsumesSingleMediaTypeTestController.kt ================================================ package test.micronaut.consumes.onfunction.onlyconsumes.singlemediatype import io.micronaut.http.annotation.Body import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post import test.micronaut.Todo @Controller("/todos", consumes = ["text/plain"]) @Suppress("UNUSED_PARAMETER") class ConsumesSingleMediaTypeTestController { @Post fun todos(@Body todo: Todo) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/deprecation/none/NoDeprecation.kt ================================================ package test.micronaut.deprecation.none import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todos") class NoDeprecation { @Get fun todo() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/deprecation/onclass/DeprecationOnClass.kt ================================================ package test.micronaut.deprecation.onclass import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todos") @Deprecated("Test") class DeprecationOnClass { @Get fun todo() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/deprecation/onfunction/DeprecationOnFunction.kt ================================================ package test.micronaut.deprecation.onfunction import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todos") class DeprecationOnFunction { @Get @Deprecated("Test") fun todo() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/headerparameters/optional/HeaderParameterTestController.kt ================================================ package test.micronaut.headerparameters.optional import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Header @Controller("/todos") @Suppress("UNUSED_PARAMETER") class HeaderParameterTestController { @Get fun todos(@Header("allow-cache", defaultValue = "true") otherName: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/headerparameters/required/HeaderParameterTestController.kt ================================================ package test.micronaut.headerparameters.required import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Header @Controller("/todos") @Suppress("UNUSED_PARAMETER") class HeaderParameterTestController { @Get fun todos(@Header("allow-cache") otherName: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/delete/CombinedControllerAnnotationWithDeletePathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.delete import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Delete @Controller("/todo") class CombinedControllerAnnotationWithDeletePathTestController { @Delete("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/get/CombinedControllerAnnotationWithGetPathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.get import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todo") class CombinedControllerAnnotationWithGetPathTestController { @Get("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/head/CombinedControllerAnnotationWithHeadPathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.head import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Head @Controller("/todo") class CombinedControllerAnnotationWithHeadPathTestController { @Head("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/options/CombinedControllerAnnotationWithOptionsPathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.options import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Options @Controller("/todo") class CombinedControllerAnnotationWithOptionsPathTestController { @Options("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/patch/CombinedControllerAnnotationWithPatchPathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.patch import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Patch @Controller("/todo") class CombinedControllerAnnotationWithPatchPathTestController { @Patch("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/post/CombinedControllerAnnotationWithPostPathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.post import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post @Controller("/todo") class CombinedControllerAnnotationWithPostPathTestController { @Post("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/combinedcontrollerandhttpmethodannotation/put/CombinedControllerAnnotationWithPutPathTestController.kt ================================================ package test.micronaut.path.combinedcontrollerandhttpmethodannotation.put import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Put @Controller("/todo") class CombinedControllerAnnotationWithPutPathTestController { @Put("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/firstpathsegmentwithoutleadingslash/FirstPathSegmentWithoutLeadingSlashTestController.kt ================================================ package test.micronaut.path.firstpathsegmentwithoutleadingslash import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("todo") class FirstPathSegmentWithoutLeadingSlashTestController { @Get("/list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/innerpathsegmentwithoutleadingslash/InnerPathSegmentWithoutLeadingSlashTestController.kt ================================================ package test.micronaut.path.innerpathsegmentwithoutleadingslash import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todo") class InnerPathSegmentWithoutLeadingSlashTestController { @Get("list") fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/nohttpmethodannotation/NoHttpMethodAnnotationTestController.kt ================================================ package test.micronaut.path.nohttpmethodannotation import io.micronaut.http.annotation.Controller @Controller("/todos") class NoHttpMethodAnnotationTestController { fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/delete/OnlyControllerAnnotationWithDeletePathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.delete import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Delete @Controller("/todos") class OnlyControllerAnnotationWithDeletePathTestController { @Delete fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/get/OnlyControllerAnnotationWithGetPathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.get import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todos") class OnlyControllerAnnotationWithGetPathTestController { @Get fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/head/OnlyControllerAnnotationWithHeadPathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.head import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Head @Controller("/todos") class OnlyControllerAnnotationWithHeadPathTestController { @Head fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/options/OnlyControllerAnnotationWithOptionsPathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.options import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Options @Controller("/todos") class OnlyControllerAnnotationWithOptionsPathTestController { @Options fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/patch/OnlyControllerAnnotationWithPatchPathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.patch import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Patch @Controller("/todos") class OnlyControllerAnnotationWithPatchPathTestController { @Patch fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/post/OnlyControllerAnnotationWithPostPathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.post import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Post @Controller("/todos") class OnlyControllerAnnotationWithPostPathTestController { @Post fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/path/onlycontrollerannotation/put/OnlyControllerAnnotationWithPutPathTestController.kt ================================================ package test.micronaut.path.onlycontrollerannotation.put import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Put @Controller("/todos") class OnlyControllerAnnotationWithPutPathTestController { @Put fun todos() { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/pathparameters/annotation/name/PathParameterDefinedByAnnotationTestController.kt ================================================ package test.micronaut.pathparameters.annotation.name import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.PathVariable @Controller("/todos/{id}") @Suppress("UNUSED_PARAMETER") class PathParameterDefinedByAnnotationTestController { @Get fun todos(@PathVariable(name = "id") otherName: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/pathparameters/annotation/value/PathParameterDefinedByAnnotationTestController.kt ================================================ package test.micronaut.pathparameters.annotation.value import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.PathVariable @Controller("/todos/{id}") @Suppress("UNUSED_PARAMETER") class PathParameterDefinedByAnnotationTestController { @Get fun todos(@PathVariable("id") otherName: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/pathparameters/variable/PathParameterDefinedByVariableNameTestController.kt ================================================ package test.micronaut.pathparameters.variable import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todos/{id}") @Suppress("UNUSED_PARAMETER") class PathParameterDefinedByVariableNameTestController { @Get fun todos(id: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/default/DefaultTestController.kt ================================================ package test.micronaut.produces.default import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import test.micronaut.Todo @Controller("/todos") class ProducesDefaultMediaTypeTestController { @Get fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onclass/onlycontroller/multiplemediatypes/ProducesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.produces.onclass.onlycontroller.multiplemediatypes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import test.micronaut.Todo @Controller("/todos", produces = ["text/plain", "application/xml"]) class ProducesMultipleMediaTypesTestController { @Get fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onclass/onlycontroller/singlemediatype/ProducesSingleMediaTypeTestController.kt ================================================ package test.micronaut.produces.onclass.onlycontroller.singlemediatype import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import test.micronaut.Todo @Controller("/todos", produces = ["text/plain"]) class ProducesSingleMediaTypeTestController { @Get fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onclass/producesoverridescontroller/multiplemediatypes/ProducesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.produces.onclass.producesoverridescontroller.multiplemediatypes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import test.micronaut.Todo @Controller("/todos", produces = ["text/plain", "application/xml"]) @Produces("application/json", "application/pdf") class ProducesMultipleMediaTypesTestController { @Get fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onclass/producesoverridescontroller/singlemediatype/ProducesSingleMediaTypeTestController.kt ================================================ package test.micronaut.produces.onclass.producesoverridescontroller.singlemediatype import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import test.micronaut.Todo @Produces("application/xml") @Controller("/todos", produces = ["text/plain"]) class ProducesSingleMediaTypeTestController { @Get fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onfunction/onlyproduces/multiplemediatypes/ProducesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.produces.onfunction.onlyproduces.multiplemediatypes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import test.micronaut.Todo @Controller("/todos") class ProducesMultipleMediaTypesTestController { @Get @Produces("text/plain", "application/xml") fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onfunction/onlyproduces/singlemediatype/ProducesSingleMediaTypeTestController.kt ================================================ package test.micronaut.produces.onfunction.onlyproduces.singlemediatype import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import test.micronaut.Todo @Controller("/todos") class ProducesSingleMediaTypeTestController { @Get @Produces("text/plain") fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onfunction/producesoverridescontroller/multiplemediatypes/ProducesMultipleMediaTypesTestController.kt ================================================ package test.micronaut.produces.onfunction.producesoverridescontroller.multiplemediatypes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import test.micronaut.Todo @Controller("/todos", produces = ["text/plain", "application/xml"]) class ProducesMultipleMediaTypesTestController { @Get @Produces("application/json", "application/pdf") fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/produces/onfunction/producesoverridescontroller/singlemediatype/ProducesSingleMediaTypeTestController.kt ================================================ package test.micronaut.produces.onfunction.producesoverridescontroller.singlemediatype import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.Produces import test.micronaut.Todo @Controller("/todos", produces = ["text/plain"]) class ProducesSingleMediaTypeTestController { @Get @Produces("application/xml") fun todos() = Todo() } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/queryparameters/optional/QueryParameterTestController.kt ================================================ package test.micronaut.queryparameters.optional import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.QueryValue @Controller("/todos") @Suppress("UNUSED_PARAMETER") class QueryParameterTestController { @Get fun todos(@QueryValue("filter", defaultValue = "all") filter: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/queryparameters/required/annotation/QueryParameterTestController.kt ================================================ package test.micronaut.queryparameters.required.annotation import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get import io.micronaut.http.annotation.QueryValue @Controller("/todos") @Suppress("UNUSED_PARAMETER") class QueryParameterTestController { @Get fun todos(@QueryValue("filter") filter: String) { } } ================================================ FILE: micronaut/src/test/kotlin/test/micronaut/queryparameters/required/withoutannotation/QueryParameterTestController.kt ================================================ package test.micronaut.queryparameters.required.withoutannotation import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Get @Controller("/todos") @Suppress("UNUSED_PARAMETER") class QueryParameterTestController { @Get fun todos(filter: String) { } } ================================================ FILE: micronaut/src/test/resources/resources/.gitemptydir ================================================ ================================================ FILE: openapi/README.md ================================================ # hikaku - OpenAPI Supports OpenAPI 3.0.X ## Feature Support Please refer to the list of [all features](../docs/features.md). To check the feature support for this converter. ## Usage The OpenApiConverter can be used either with a `File` or a `Path` object. Both objects can relate to a `*.yaml`/`*.yml` or `*.json` file. ================================================ FILE: openapi/build.gradle ================================================ group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-openapi' dependencies { api project(':core') api 'io.swagger.parser.v3:swagger-parser-v3:2.0.27' } uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-openapi' description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains a converter for OpenAPI specifications.' } } } } ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverter.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.AbstractEndpointConverter import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.converters.openapi.extensions.httpMethods import de.codecentric.hikaku.converters.openapi.extractors.* import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod import de.codecentric.hikaku.extensions.checkFileValidity import io.swagger.v3.oas.models.Operation import io.swagger.v3.parser.OpenAPIV3Parser import java.io.File import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Files.readAllLines import java.nio.file.Path /** * Extracts and converts [Endpoint]s from OpenAPI 3.0.X document. Either a *.yaml*, *.yml* or a *.json* file. */ class OpenApiConverter private constructor(private val specificationContent: String) : AbstractEndpointConverter() { @JvmOverloads constructor(openApiSpecification: File, charset: Charset = UTF_8): this(openApiSpecification.toPath(), charset) @JvmOverloads constructor(openApiSpecification: Path, charset: Charset = UTF_8): this(readFileContent(openApiSpecification, charset)) override val supportedFeatures = SupportedFeatures( Feature.QueryParameters, Feature.PathParameters, Feature.HeaderParameters, Feature.Produces, Feature.Consumes, Feature.Deprecation ) override fun convert(): Set { try { return parseOpenApi() } catch (throwable: Throwable) { throw EndpointConverterException(throwable) } } private fun parseOpenApi(): Set { val swaggerParseResult = OpenAPIV3Parser().readContents(specificationContent, null, null) val openApi = swaggerParseResult.openAPI ?: throw openApiParseException(swaggerParseResult.messages) val extractConsumesMediaTypes = ConsumesExtractor(openApi) val extractProduceMediaTypes = ProducesExtractor(openApi) val extractQueryParameters = QueryParameterExtractor(openApi) val extractHeaderParameters = HeaderParameterExtractor(openApi) val extractPathParameters = PathParameterExtractor(openApi) return openApi.paths.flatMap { (path, pathItem) -> val commonQueryParameters = extractQueryParameters(pathItem.parameters) val commonPathParameters = extractPathParameters(pathItem.parameters) val commonHeaderParameters = extractHeaderParameters(pathItem.parameters) pathItem.httpMethods().map { (httpMethod: HttpMethod, operation: Operation?) -> Endpoint( path = path, httpMethod = httpMethod, queryParameters = commonQueryParameters.union(extractQueryParameters(operation?.parameters)), pathParameters = commonPathParameters.union(extractPathParameters(operation?.parameters)), headerParameters = commonHeaderParameters.union(extractHeaderParameters(operation?.parameters)), consumes = extractConsumesMediaTypes(operation), produces = extractProduceMediaTypes(operation), deprecated = operation?.deprecated ?: false ) } } .toSet() } } private fun readFileContent(openApiSpecification: Path, charset: Charset): String { try { openApiSpecification.checkFileValidity(".json", ".yaml", ".yml") } catch (throwable: Throwable) { throw EndpointConverterException(throwable) } val fileContent = readAllLines(openApiSpecification, charset).joinToString("\n") if (fileContent.isBlank()) { throw EndpointConverterException("Given OpenAPI file is blank.") } return fileContent } private fun openApiParseException(reasons: List) = EndpointConverterException("Failed to parse OpenAPI spec. Reasons:\n${reasons.joinToString("\n")}") ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extensions/PathItemExtensions.kt ================================================ package de.codecentric.hikaku.converters.openapi.extensions import io.swagger.v3.oas.models.Operation import io.swagger.v3.oas.models.PathItem import de.codecentric.hikaku.endpoints.HttpMethod import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.HttpMethod.POST import de.codecentric.hikaku.endpoints.HttpMethod.PATCH import de.codecentric.hikaku.endpoints.HttpMethod.DELETE import de.codecentric.hikaku.endpoints.HttpMethod.PUT import de.codecentric.hikaku.endpoints.HttpMethod.OPTIONS import de.codecentric.hikaku.endpoints.HttpMethod.HEAD import de.codecentric.hikaku.endpoints.HttpMethod.TRACE internal fun PathItem.httpMethods() = mapOf( GET to get, POST to post, PATCH to patch, DELETE to delete, PUT to put, OPTIONS to options, HEAD to head, TRACE to trace ).filterValues { it != null } ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extensions/ReferencedSchema.kt ================================================ package de.codecentric.hikaku.converters.openapi.extensions import io.swagger.v3.oas.models.parameters.Parameter import io.swagger.v3.oas.models.parameters.RequestBody import io.swagger.v3.oas.models.responses.ApiResponse internal val ApiResponse.referencedSchema get() = `$ref` internal val RequestBody.referencedSchema get() = `$ref` internal val Parameter.referencedSchema get() = `$ref` ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extractors/ConsumesExtractor.kt ================================================ package de.codecentric.hikaku.converters.openapi.extractors import de.codecentric.hikaku.converters.openapi.extensions.referencedSchema import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.Operation internal class ConsumesExtractor(private val openApi: OpenAPI) { operator fun invoke(operation: Operation?): Set { return operation?.requestBody ?.content ?.keys .orEmpty() .union(extractConsumesFromComponents(operation)) .toSet() } private fun extractConsumesFromComponents(operation: Operation?): Set { return operation?.requestBody ?.referencedSchema ?.let { Regex("#/components/requestBodies/(?.+)") .find(it) ?.groups ?.get("key") ?.value } ?.let { openApi.components .requestBodies[it] ?.content ?.keys } .orEmpty() } } ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extractors/HeaderParameterExtractor.kt ================================================ package de.codecentric.hikaku.converters.openapi.extractors import de.codecentric.hikaku.converters.openapi.extensions.referencedSchema import de.codecentric.hikaku.endpoints.HeaderParameter import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.parameters.Parameter as OpenApiParameter import io.swagger.v3.oas.models.parameters.HeaderParameter as OpenApiHeaderParameter internal class HeaderParameterExtractor(private val openApi: OpenAPI) { operator fun invoke(parameters: List?): Set { return extractInlineHeaderParameters(parameters).union(extractHeaderParametersFromComponents(parameters)) } private fun extractInlineHeaderParameters(parameters: List?): Set { return parameters ?.filterIsInstance() ?.map { HeaderParameter(it.name, it.required) } .orEmpty() .toSet() } private fun extractHeaderParametersFromComponents(parameters: List?): Set { return parameters ?.filter { it.referencedSchema != null } ?.map { Regex("#/components/parameters/(?.+)") .find(it.referencedSchema) ?.groups ?.get("key") ?.value } ?.map { openApi.components .parameters[it] } ?.filter { it?.`in` == "header" } ?.map { HeaderParameter(it?.name ?: "", it?.required ?: false) } .orEmpty() .toSet() } } ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extractors/PathParameterExtractor.kt ================================================ package de.codecentric.hikaku.converters.openapi.extractors import de.codecentric.hikaku.converters.openapi.extensions.referencedSchema import de.codecentric.hikaku.endpoints.PathParameter import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.parameters.PathParameter as OpenApiPathParameter import io.swagger.v3.oas.models.parameters.Parameter as OpenApiParameter internal class PathParameterExtractor(private val openApi: OpenAPI) { operator fun invoke(parameters: List?): Set { return extractInlinePathParameters(parameters).union(extractPathParametersFromComponents(parameters)) } private fun extractInlinePathParameters(parameters: List?): Set { return parameters ?.filterIsInstance() ?.map { PathParameter(it.name) } .orEmpty() .toSet() } private fun extractPathParametersFromComponents(parameters: List?): Set { return parameters ?.filter { it.referencedSchema != null } ?.map { Regex("#/components/parameters/(?.+)") .find(it.referencedSchema) ?.groups ?.get("key") ?.value } ?.map { openApi.components .parameters[it] } ?.filter { it?.`in` == "path" } ?.map { PathParameter(it?.name ?: "") } .orEmpty() .toSet() } } ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extractors/ProducesExtractor.kt ================================================ package de.codecentric.hikaku.converters.openapi.extractors import de.codecentric.hikaku.converters.openapi.extensions.referencedSchema import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.Operation internal class ProducesExtractor(private val openApi: OpenAPI) { operator fun invoke(operation: Operation?): Set { return operation?.responses ?.flatMap { it.value ?.content ?.keys .orEmpty() } .orEmpty() .union(extractResponsesFromComponents(operation)) .toSet() } private fun extractResponsesFromComponents(operation: Operation?): Set { return operation?.responses ?.mapNotNull { it.value.referencedSchema } ?.map { Regex("#/components/responses/(?.+)") .find(it) ?.groups ?.get("key") ?.value } ?.flatMap { openApi.components .responses[it] ?.content ?.keys .orEmpty() } .orEmpty() .toSet() } } ================================================ FILE: openapi/src/main/kotlin/de/codecentric/hikaku/converters/openapi/extractors/QueryParameterExtractor.kt ================================================ package de.codecentric.hikaku.converters.openapi.extractors import de.codecentric.hikaku.converters.openapi.extensions.referencedSchema import de.codecentric.hikaku.endpoints.QueryParameter import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.parameters.Parameter as OpenApiParameter import io.swagger.v3.oas.models.parameters.QueryParameter as OpenApiQueryParameter internal class QueryParameterExtractor(private val openApi: OpenAPI) { operator fun invoke(parameters: List?): Set { return extractInlineQueryParameters(parameters).union(extractQueryParametersFromComponents(parameters)) } private fun extractInlineQueryParameters(parameters: List?): Set { return parameters ?.filterIsInstance() ?.map { QueryParameter(it.name, it.required) } .orEmpty() .toSet() } private fun extractQueryParametersFromComponents(parameters: List?): Set { return parameters ?.filter { it.referencedSchema != null } ?.map { Regex("#/components/parameters/(?.+)") .find(it.referencedSchema) ?.groups ?.get("key") ?.value } ?.map { openApi.components .parameters[it] } ?.filter { it?.`in` == "query" } ?.map { QueryParameter(it?.name ?: "", it?.required ?: false) } .orEmpty() .toSet() } } ================================================ FILE: openapi/src/main/resources/.gitemptydir ================================================ ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterConsumesTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.POST import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterConsumesTest { @Test fun `inline declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/consumes_inline.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf("application/xml") ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `response is declared in components section`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/consumes_requestbody_in_components.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf("application/xml") ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterDeprecationTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterDeprecationTest { @Test fun `no deprecation`() { //given val file = Paths.get(this::class.java.classLoader.getResource("deprecation/deprecation_none.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("application/json"), deprecated = false ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `deprecated operation`() { //given val file = Paths.get(this::class.java.classLoader.getResource("deprecation/deprecation_operation.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("application/json"), deprecated = true ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterEndpointTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterEndpointTest { @Test fun `extract two different paths`() { //given val file = Paths.get(this::class.java.classLoader.getResource("endpoints/endpoints_two_different_paths.yaml").toURI()) val implementation = setOf( Endpoint("/todos", GET), Endpoint("/tags", GET) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `extract two paths of which one is nested`() { //given val file = Paths.get(this::class.java.classLoader.getResource("endpoints/endpoints_two_nested_paths.yaml").toURI()) val implementation = setOf( Endpoint("/todos", GET), Endpoint("/todos/query", GET) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `extract all http methods`() { //given val file = Paths.get(this::class.java.classLoader.getResource("endpoints/endpoints_all_http_methods.yaml").toURI()) val implementation = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS), Endpoint("/todos", TRACE) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterHeaderParameterTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.HeaderParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterHeaderParameterTest { @Test fun `header parameter inline declaration on Operation object`() { //given val file = Paths.get(this::class.java.classLoader.getResource("header_parameter/header_parameter_inline.yaml").toURI()) val headerParameters = setOf( HeaderParameter("x-b3-traceid", false), HeaderParameter("allow-cache", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].headerParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(headerParameters) } @Test fun `one header parameter declared inline and one parameter referenced from parameters section in components`() { //given val file = Paths.get(this::class.java.classLoader.getResource("header_parameter/header_parameter_in_components.yaml").toURI()) val headerParameters = setOf( HeaderParameter("x-b3-traceid", false), HeaderParameter("allow-cache", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].headerParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(headerParameters) } @Test fun `common header parameter inline declaration on Operation object`() { //given val file = Paths.get(this::class.java.classLoader.getResource("header_parameter/common_header_parameter_inline.yaml").toURI()) val headerParameters = setOf( HeaderParameter("x-b3-traceid", false), HeaderParameter("allow-cache", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].headerParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(headerParameters) } @Test fun `one common header parameter declared inline and one parameter referenced from parameters section in components`() { //given val file = Paths.get(this::class.java.classLoader.getResource("header_parameter/common_header_parameter_in_components.yaml").toURI()) val headerParameters = setOf( HeaderParameter("x-b3-traceid", false), HeaderParameter("allow-cache", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].headerParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(headerParameters) } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterInvalidInputTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.converters.EndpointConverterException import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.io.File import java.nio.file.Paths import kotlin.test.assertFailsWith class OpenApiConverterInvalidInputTest { @Nested inner class PathObjectTests { @Test fun `empty file returns an empty list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/empty_file.yaml").toURI()) //when assertFailsWith { OpenApiConverter(file).conversionResult } } @Test fun `file consisting solely of whitespaces returns an empty list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/whitespaces_only_file.yaml").toURI()) //when assertFailsWith { OpenApiConverter(file).conversionResult } } @Test fun `OpenAPI yaml file containing syntax error`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/syntax_error.yaml").toURI()) val converter = OpenApiConverter(file) //when assertFailsWith { converter.conversionResult } } } @Nested inner class FileObjectTests { @Test fun `empty file returns an empty list`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/empty_file.yaml").toURI()) //when assertFailsWith { OpenApiConverter(file).conversionResult } } @Test fun `file consisting solely of whitespaces returns an empty list`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/whitespaces_only_file.yaml").toURI()) //when assertFailsWith { OpenApiConverter(file).conversionResult } } @Test fun `OpenAPI yaml file containing syntax error`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/syntax_error.yaml").toURI()) val converter = OpenApiConverter(file) //when assertFailsWith { converter.conversionResult } } } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterPathParameterTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterPathParameterTest { @Test fun `path parameter inline declaration on Operation object`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameter/path_parameter_inline.yaml").toURI()) val pathParameter = PathParameter("id") //when val result = OpenApiConverter(file).conversionResult.toList() //then assertThat(result[0].pathParameters).containsExactly(pathParameter) assertThat(result[1].pathParameters).containsExactly(pathParameter) assertThat(result[2].pathParameters).containsExactly(pathParameter) } @Test fun `one path parameter declared inline and two parameters referenced from parameters section in components`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameter/path_parameter_in_components.yaml").toURI()) val pathParameter = PathParameter("id") //when val result = OpenApiConverter(file).conversionResult.toList() //then assertThat(result[0].pathParameters).containsExactly(pathParameter) assertThat(result[1].pathParameters).containsExactly(pathParameter) assertThat(result[2].pathParameters).containsExactly(pathParameter) } @Test fun `common path parameter inline declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameter/common_path_parameter_inline.yaml").toURI()) val pathParameter = PathParameter("id") //when val result = OpenApiConverter(file).conversionResult.toList() //then assertThat(result[0].pathParameters).containsExactly(pathParameter) assertThat(result[1].pathParameters).containsExactly(pathParameter) assertThat(result[2].pathParameters).containsExactly(pathParameter) } @Test fun `common path parameter referenced from parameters section in components`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameter/common_path_parameter_in_components.yaml").toURI()) val pathParameter = PathParameter("id") //when val result = OpenApiConverter(file).conversionResult.toList() //then assertThat(result[0].pathParameters).containsExactly(pathParameter) assertThat(result[1].pathParameters).containsExactly(pathParameter) assertThat(result[2].pathParameters).containsExactly(pathParameter) } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterProducesTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.DELETE import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterProducesTest { @Test fun `inline declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_inline.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("application/json") ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `no content-type`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_no_content_type.yaml").toURI()) val implementation = setOf( Endpoint("/todos", DELETE) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `response is declared in components section`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_response_in_components.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("application/xml") ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `produces having a default value`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_with_default.yaml").toURI()) val implementation = setOf( Endpoint( path = "/todos/query", httpMethod = GET, produces = setOf("application/json", "text/plain") ) ) //when val specification = OpenApiConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } } ================================================ FILE: openapi/src/test/kotlin/de/codecentric/hikaku/converters/openapi/OpenApiConverterQueryParameterTest.kt ================================================ package de.codecentric.hikaku.converters.openapi import de.codecentric.hikaku.endpoints.QueryParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class OpenApiConverterQueryParameterTest { @Test fun `query parameter inline declaration on Operation object`() { //given val file = Paths.get(this::class.java.classLoader.getResource("query_parameter/query_parameter_inline.yaml").toURI()) val queryParameters = setOf( QueryParameter("tag", false), QueryParameter("limit", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].queryParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(queryParameters) } @Test fun `one query parameter declared inline and one parameter referenced from parameters section in components`() { //given val file = Paths.get(this::class.java.classLoader.getResource("query_parameter/query_parameter_in_components.yaml").toURI()) val queryParameters = setOf( QueryParameter("tag", false), QueryParameter("limit", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].queryParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(queryParameters) } @Test fun `common query parameter inline declaration on Operation object`() { //given val file = Paths.get(this::class.java.classLoader.getResource("query_parameter/common_query_parameter_inline.yaml").toURI()) val queryParameters = setOf( QueryParameter("tag", false), QueryParameter("limit", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].queryParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(queryParameters) } @Test fun `one query parameter declared inline and one common parameter referenced from parameters section in components`() { //given val file = Paths.get(this::class.java.classLoader.getResource("query_parameter/common_query_parameter_in_components.yaml").toURI()) val queryParameters = setOf( QueryParameter("tag", false), QueryParameter("limit", true) ) //when val result = OpenApiConverter(file).conversionResult.toList()[0].queryParameters //then assertThat(result).containsExactlyInAnyOrderElementsOf(queryParameters) } } ================================================ FILE: openapi/src/test/resources/consumes/consumes_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: post: description: '' requestBody: content: 'application/xml': schema: $ref: '#/components/schemas/Todo' responses: '200': description: OK components: schemas: Todo: type: array items: type: string ================================================ FILE: openapi/src/test/resources/consumes/consumes_requestbody_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: post: description: '' requestBody: $ref: '#/components/requestBodies/TodoRequest' responses: '200': description: '' components: requestBodies: TodoRequest: description: A complex object array response content: application/xml: schema: type: array items: $ref: '#/components/schemas/Todo' schemas: Todo: type: array items: type: string NotFoundModel: type: string ================================================ FILE: openapi/src/test/resources/deprecation/deprecation_none.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: deprecated: false description: '' responses: '200': description: OK content: application/json: schema: type: array items: type: string ================================================ FILE: openapi/src/test/resources/deprecation/deprecation_operation.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: deprecated: true description: '' responses: '200': description: OK content: application/json: schema: type: array items: type: string ================================================ FILE: openapi/src/test/resources/endpoints/endpoints_all_http_methods.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: '' responses: '200': description: OK post: description: '' responses: '200': description: OK put: description: '' responses: '200': description: OK patch: description: '' responses: '200': description: OK delete: description: '' responses: '200': description: OK head: description: '' responses: '200': description: OK options: description: '' responses: '200': description: OK trace: description: '' responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/endpoints/endpoints_two_different_paths.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: '' responses: '200': description: OK /tags: get: description: '' responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/endpoints/endpoints_two_nested_paths.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: '' responses: '200': description: OK /todos/query: get: description: '' responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/header_parameter/common_header_parameter_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: parameters: - name: x-b3-traceid in: header required: false description: "Trace id." schema: type: string - $ref: "#/components/parameters/allowCacheParam" get: description: 'Get all todos' responses: '200': description: OK components: parameters: allowCacheParam: name: allow-cache in: header required: true description: "Whether cached data is allowed or not." schema: type: boolean ================================================ FILE: openapi/src/test/resources/header_parameter/common_header_parameter_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: parameters: - name: x-b3-traceid in: header required: false description: "Trace id." schema: type: string - name: allow-cache in: header required: true description: "Whether cached data is allowed or not." schema: type: boolean get: description: 'Get all todos' responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/header_parameter/header_parameter_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: 'Get all todos' parameters: - name: x-b3-traceid in: header required: false description: "Trace id." schema: type: string - $ref: "#/components/parameters/allowCacheParam" responses: '200': description: OK components: parameters: allowCacheParam: name: allow-cache in: header required: true description: "Whether cached data is allowed or not." schema: type: boolean ================================================ FILE: openapi/src/test/resources/header_parameter/header_parameter_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: 'Get all todos' parameters: - name: x-b3-traceid in: header required: false description: "Trace id." schema: type: string - name: allow-cache in: header required: true description: "Whether cached data is allowed or not." schema: type: boolean responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/invalid_input/empty_file.yaml ================================================ ================================================ FILE: openapi/src/test/resources/invalid_input/syntax_error.yaml ================================================ openapi: 3.0.2 info: title: Todo List paths: todos get summary: Add a new todo description: 'Creates a new todo.' responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/invalid_input/whitespaces_only_file.yaml ================================================ ================================================ FILE: openapi/src/test/resources/path_parameter/common_path_parameter_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos/{id}: parameters: - $ref: "#/components/parameters/todoIdParam" get: description: 'Get all todos' responses: '200': description: OK post: description: 'Create a new todo.' responses: '200': description: OK delete: description: 'Delete a todo entry.' responses: '200': description: OK components: parameters: todoIdParam: name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 ================================================ FILE: openapi/src/test/resources/path_parameter/common_path_parameter_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos/{id}: parameters: - name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 get: description: 'Get all todos' responses: '200': description: OK post: description: 'Create a new todo.' responses: '200': description: OK delete: description: 'Delete a todo entry.' responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/path_parameter/path_parameter_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos/{id}: get: description: 'Get all todos' parameters: - name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 responses: '200': description: OK post: description: 'Create a new todo.' parameters: - $ref: "#/components/parameters/todoIdParam" responses: '200': description: OK delete: description: 'Delete a todo entry.' parameters: - $ref: "#/components/parameters/todoIdParam" responses: '200': description: OK components: parameters: todoIdParam: name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 ================================================ FILE: openapi/src/test/resources/path_parameter/path_parameter_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos/{id}: get: description: 'Get all todos' parameters: - name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 responses: '200': description: OK post: description: 'Create a new todo.' parameters: - name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 responses: '200': description: OK delete: description: 'Delete a todo entry.' parameters: - name: id in: path required: true description: "Identifier of a specific todo entry." schema: type: integer format: int32 responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/produces/produces_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: '' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Todo' components: schemas: Todo: type: array items: type: string ================================================ FILE: openapi/src/test/resources/produces/produces_no_content_type.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: delete: description: 'Delete all todos' responses: '204': description: '' ================================================ FILE: openapi/src/test/resources/produces/produces_response_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: '' responses: '200': $ref: '#/components/responses/TodoResponse' components: responses: TodoResponse: description: A complex object array response content: application/xml: schema: type: array items: $ref: '#/components/schemas/Todo' schemas: Todo: type: array items: type: string NotFoundModel: type: string ================================================ FILE: openapi/src/test/resources/produces/produces_with_default.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos/query: get: description: '' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Todo' default: description: No data content: text/plain: schema: $ref: '#/components/schemas/NotFoundModel' components: responses: TodoResponse: description: A complex object array response content: application/xml: schema: type: array items: $ref: '#/components/schemas/Todo' schemas: Todo: type: array items: type: string NotFoundModel: type: string ================================================ FILE: openapi/src/test/resources/query_parameter/common_query_parameter_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: parameters: - $ref: "#/components/parameters/limitParam" get: description: 'Get all todos' parameters: - name: tag in: query required: false description: "Filter todos by a given tag." schema: type: string responses: '200': description: OK components: parameters: limitParam: name: limit in: query required: true description: "Limit" schema: type: number format: int32 ================================================ FILE: openapi/src/test/resources/query_parameter/common_query_parameter_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: parameters: - name: limit in: query required: true description: "Limit" schema: type: number format: int32 get: description: 'Get all todos' parameters: - name: tag in: query required: false description: "Filter todos by a given tag." schema: type: string responses: '200': description: OK ================================================ FILE: openapi/src/test/resources/query_parameter/query_parameter_in_components.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: 'Get all todos' parameters: - name: tag in: query required: false description: "Filter todos by a given tag." schema: type: string - $ref: "#/components/parameters/limitParam" responses: '200': description: OK components: parameters: limitParam: name: limit in: query required: true description: "Limit" schema: type: number format: int32 ================================================ FILE: openapi/src/test/resources/query_parameter/query_parameter_inline.yaml ================================================ openapi: 3.0.2 info: version: 1.0.0 title: Todo List paths: /todos: get: description: 'Get all todos' parameters: - name: tag in: query required: false description: "Filter todos by a given tag." schema: type: string - name: limit in: query required: true description: "Limit" schema: type: number format: int32 responses: '200': description: OK ================================================ FILE: raml/README.md ================================================ # hikaku - RAML Supports RAML 1.X ## Feature Support Please refer to the list of [all features](../docs/features.md). To check the feature support for this converter. ## Currently not supported * Query string declaration * see https://github.com/raml-org/raml-spec/blob/master/versions/raml-10/raml-10.md/#query-strings-and-query-parameters ## Usage The RamlConverter can be used either with a `File` or a `Path` object. ================================================ FILE: raml/build.gradle ================================================ group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-raml' dependencies { api project(':core') api 'org.raml:raml-parser-2:1.0.51' testImplementation "io.mockk:mockk:1.12.0" } uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-raml' description = 'A library to test if the implementation of a REST-API meets its specification. This module contains a converter for raml specifications.' } } } } ================================================ FILE: raml/src/main/kotlin/de/codecentric/hikaku/converters/raml/RamlConverter.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.AbstractEndpointConverter import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.converters.raml.extensions.* import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.extensions.checkFileValidity import org.raml.v2.api.RamlModelBuilder import org.raml.v2.api.RamlModelResult import org.raml.v2.api.model.v10.resources.Resource import java.io.File import java.nio.file.Path class RamlConverter(private val ramlSpecification: File) : AbstractEndpointConverter() { constructor(ramlSpecification: Path) : this(ramlSpecification.toFile()) override val supportedFeatures = SupportedFeatures( Feature.QueryParameters, Feature.PathParameters, Feature.HeaderParameters, Feature.Produces, Feature.Consumes, Feature.Deprecation ) override fun convert(): Set { val ramlParserResult: RamlModelResult? try { ramlSpecification.checkFileValidity(".raml") ramlParserResult = RamlModelBuilder().buildApi(ramlSpecification) } catch(throwable: Throwable) { throw EndpointConverterException(throwable) } if (ramlParserResult.isVersion08) { throw EndpointConverterException("Unsupported RAML version") } if (ramlParserResult.hasErrors()) { throw EndpointConverterException(ramlParserResult.validationResults.joinToString("\n")) } return ramlParserResult.apiV10?.resources()?.let { resourceList -> resourceList.flatMap { findEndpoints(it) } } .orEmpty() .toSet() } private fun findEndpoints(resource: Resource): List { val endpoints = mutableListOf() if (resource.resources().isNotEmpty()) { endpoints += resource.resources().flatMap { return@flatMap findEndpoints(it) } } if (resource.methods().isNotEmpty()) { endpoints += resource.methods().map { Endpoint( path = resource.resourcePath(), httpMethod = it.hikakuHttpMethod(), queryParameters = it.hikakuQueryParameters(), pathParameters = it.resource()?.hikakuPathParameters().orEmpty(), headerParameters = it?.hikakuHeaderParameters().orEmpty(), consumes = it.requestMediaTypes(), produces = it.responseMediaTypes(), deprecated = it.isEndpointDeprecated() ) } } return endpoints } } ================================================ FILE: raml/src/main/kotlin/de/codecentric/hikaku/converters/raml/extensions/MethodExtensions.kt ================================================ package de.codecentric.hikaku.converters.raml.extensions import de.codecentric.hikaku.endpoints.HeaderParameter import de.codecentric.hikaku.endpoints.HttpMethod import de.codecentric.hikaku.endpoints.QueryParameter import org.raml.v2.api.model.v10.methods.Method internal fun Method.hikakuHttpMethod() = HttpMethod.valueOf(this.method().uppercase()) internal fun Method.hikakuQueryParameters(): Set { return this.queryParameters() .map { QueryParameter(it.name(), it.required()) } .toSet() } internal fun Method.hikakuHeaderParameters(): Set { return this.headers() .map { HeaderParameter(it.name(), it.required()) } .toSet() } internal fun Method.requestMediaTypes(): Set { return this.body().map { it.name() } .toSet() } internal fun Method.responseMediaTypes(): Set { return this.responses().flatMap {response -> response.body().map { it.name() } } .toSet() } internal fun Method.isEndpointDeprecated() = this.annotations().any { i -> i.annotation().name() == "deprecated" } || checkNotNull(this.resource()).annotations().any { i -> i.annotation().name() == "deprecated" } ================================================ FILE: raml/src/main/kotlin/de/codecentric/hikaku/converters/raml/extensions/ResourceExtensions.kt ================================================ package de.codecentric.hikaku.converters.raml.extensions import de.codecentric.hikaku.endpoints.PathParameter import org.raml.v2.api.model.v10.resources.Resource fun Resource.hikakuPathParameters(): Set { return this.uriParameters() .map { PathParameter(it.name()) } .toSet() } ================================================ FILE: raml/src/main/resources/.gitemptydir ================================================ ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterConsumesTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths import kotlin.test.assertFailsWith class RamlConverterConsumesTest { @Test fun `no media type`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/no_media_type.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `single default media type`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/single_default_media_type.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple default media types`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/multiple_default_media_types.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "application/xml" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `single method declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/single_method_declaration.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "text/plain" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple method declarations`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/multiple_method_declarations.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "application/xml" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `method declaration overwrites default`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/method_declaration_overwrites_default.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "text/plain" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterDeprecationTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class RamlConverterDeprecationTest { @Test fun `no deprecations`() { //given val file = Paths.get(this::class.java.classLoader.getResource("deprecation/none.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("text/plain"), deprecated = false ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `deprecated resource`() { //given val file = Paths.get(this::class.java.classLoader.getResource("deprecation/on_resource.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("text/plain"), deprecated = true ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `deprecated method`() { //given val file = Paths.get(this::class.java.classLoader.getResource("deprecation/on_method.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf("text/plain"), deprecated = true ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterHeaderParameterTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HeaderParameter import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class RamlConverterHeaderParameterTest { @Test fun `extract an optional and a required header parameter`() { //given val file = Paths.get(this::class.java.classLoader.getResource("header_parameter.raml").toURI()) val specification = setOf( Endpoint( path ="/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true), HeaderParameter("x-b3-traceid", false) ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterHttpMethodTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class RamlConverterHttpMethodTest { @Test fun `extract all supported http methods`() { //given val file = Paths.get(this::class.java.classLoader.getResource("http_method/http_methods.raml").toURI()) val specification = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `path without http methods does not create an endpoint`() { //given val file = Paths.get(this::class.java.classLoader.getResource("http_method/path_without_http_method.raml").toURI()) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).isEmpty() } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterInvalidInputTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.converters.EndpointConverterException import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.io.File import java.nio.file.Paths import kotlin.test.assertFailsWith class RamlConverterInvalidInputTest { @Nested inner class FileObjectTests { @Test fun `empty file returns an empty list`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/empty_file.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `file consisting solely of whitespaces returns an empty list`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/whitespaces_only_file.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `invalid RAML version`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/invalid_raml_version.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `file containing syntax error throws SpecificationParserException`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/syntax_error.raml").toURI()) val converter = RamlConverter(file) //when assertFailsWith { converter.conversionResult } } } @Nested inner class PathObjectTests { @Test fun `empty file returns an empty list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/empty_file.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `file consisting solely of whitespaces returns an empty list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/whitespaces_only_file.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `invalid RAML version`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/invalid_raml_version.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `file containing syntax error`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/syntax_error.raml").toURI()) val converter = RamlConverter(file) //when assertFailsWith { converter.conversionResult } } } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterPathParameterTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.HttpMethod.POST import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class RamlConverterPathParameterTest { @Test fun `simple path parameter declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameter/simple_path_parameter.raml").toURI()) val specification = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `nested path parameter declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameter/nested_path_parameter.raml").toURI()) val specification = setOf( Endpoint("/todos", POST), Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterPathTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.HttpMethod.POST import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class RamlConverterPathTest { @Test fun `simple path`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path/simple_path.raml").toURI()) val specification = setOf( Endpoint("/todos", GET) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `extract nested paths`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path/nested_path.raml").toURI()) val specification = setOf( Endpoint("/todo", POST), Endpoint("/todo/list", GET) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `extract nested paths defined in a single entry`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path/nested_path_single_entry.raml").toURI()) val specification = setOf( Endpoint("/todo/list", POST), Endpoint("/todo/list", GET) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterProducesTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths import kotlin.test.assertFailsWith class RamlConverterProducesTest { @Test fun `no media type`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/no_media_type.raml").toURI()) //when assertFailsWith { RamlConverter(file).conversionResult } } @Test fun `single default media type`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/single_default_media_type.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple default media types`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/multiple_default_media_types.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/xml" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `single method declaration`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/single_method_declaration.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `multiple method declarations`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/multiple_method_declarations.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/xml" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } @Test fun `method declaration overwrites default`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/method_declaration_overwrites_default.raml").toURI()) val specification = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "text/plain" ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/kotlin/de/codecentric/hikaku/converters/raml/RamlConverterQueryParameterTest.kt ================================================ package de.codecentric.hikaku.converters.raml import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import de.codecentric.hikaku.endpoints.QueryParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class RamlConverterQueryParameterTest { @Test fun `extracts required and optional query parameter`() { //given val file = Paths.get(this::class.java.classLoader.getResource("query_parameter/query_parameter.raml").toURI()) val specification = setOf( Endpoint( path ="/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("limit", true), QueryParameter("tag", false) ) ) ) //when val implementation = RamlConverter(file).conversionResult //then assertThat(implementation).containsExactlyInAnyOrderElementsOf(specification) } } ================================================ FILE: raml/src/test/resources/consumes/method_declaration_overwrites_default.raml ================================================ #%RAML 1.0 title: test api version: v3 mediaType: [application/json, application/xml] /todos: displayName: Todos get: displayName: Get all todos body: text/plain: type: string ================================================ FILE: raml/src/test/resources/consumes/multiple_default_media_types.raml ================================================ #%RAML 1.0 title: test api version: v3 mediaType: [application/json, application/xml] /todos: displayName: Todos get: displayName: Get all todos body: type: string ================================================ FILE: raml/src/test/resources/consumes/multiple_method_declarations.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos body: application/json: type: string application/xml: type: string ================================================ FILE: raml/src/test/resources/consumes/no_media_type.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos body: type: string ================================================ FILE: raml/src/test/resources/consumes/single_default_media_type.raml ================================================ #%RAML 1.0 title: test api version: v3 mediaType: application/json /todos: displayName: Todos get: displayName: Get all todos body: type: string ================================================ FILE: raml/src/test/resources/consumes/single_method_declaration.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos body: text/plain: type: string ================================================ FILE: raml/src/test/resources/deprecation/none.raml ================================================ #%RAML 1.0 title: test api version: v3 annotationTypes: deprecated: string /todos: displayName: Todos get: displayName: Get all todos responses: 200: body: text/plain: type: string ================================================ FILE: raml/src/test/resources/deprecation/on_method.raml ================================================ #%RAML 1.0 title: test api version: v3 annotationTypes: deprecated: string /todos: displayName: Todos get: (deprecated): This method is deprecated displayName: Get all todos responses: 200: body: text/plain: type: string ================================================ FILE: raml/src/test/resources/deprecation/on_resource.raml ================================================ #%RAML 1.0 title: test api version: v3 annotationTypes: deprecated: string /todos: (deprecated): This endpoint is deprecated displayName: Todos get: displayName: Get all todos responses: 200: body: text/plain: type: string ================================================ FILE: raml/src/test/resources/header_parameter.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos headers: x-b3-traceid: displayName: x-b3-traceid type: string required: false allow-cache: displayName: allow-cache type: string required: true ================================================ FILE: raml/src/test/resources/http_method/http_methods.raml ================================================ #%RAML 1.0 title: test api /todos: displayName: Todos get: displayName: Get all todos post: displayName: Create a new todo put: displayName: Create or update a todo patch: displayName: Partially update a todo delete: displayName: Delete a todo head: displayName: check endpoint options: displayName: get allowed methods ================================================ FILE: raml/src/test/resources/http_method/path_without_http_method.raml ================================================ #%RAML 1.0 title: test api /todos: displayName: Todos ================================================ FILE: raml/src/test/resources/invalid_input/different_extension.css ================================================ ================================================ FILE: raml/src/test/resources/invalid_input/empty_file.raml ================================================ ================================================ FILE: raml/src/test/resources/invalid_input/invalid_raml_version.raml ================================================ #%RAML 0.8 title: test api version: v3 /todos: displayName: Todos get: ================================================ FILE: raml/src/test/resources/invalid_input/syntax_error.raml ================================================ #%RAML 1.0 title: test api /todos: displayN Todos ge ================================================ FILE: raml/src/test/resources/invalid_input/whitespaces_only_file.raml ================================================ ================================================ FILE: raml/src/test/resources/path/nested_path.raml ================================================ #%RAML 1.0 title: test api /todo: displayName: Todos post: displayName: Create a new todo /list: get: displayName: get all todos ================================================ FILE: raml/src/test/resources/path/nested_path_single_entry.raml ================================================ #%RAML 1.0 title: test api /todo/list: displayName: Todos post: displayName: Create a new todo get: displayName: get all todos ================================================ FILE: raml/src/test/resources/path/simple_path.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos ================================================ FILE: raml/src/test/resources/path_parameter/nested_path_parameter.raml ================================================ #%RAML 1.0 title: test api /todos: displayName: Todos post: displayName: create a new todo /{id}: get: displayName: get todo ================================================ FILE: raml/src/test/resources/path_parameter/simple_path_parameter.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos/{id}: displayName: Todos get: displayName: Get all todos ================================================ FILE: raml/src/test/resources/produces/method_declaration_overwrites_default.raml ================================================ #%RAML 1.0 title: test api version: v3 mediaType: [application/json, application/xml] /todos: displayName: Todos get: displayName: Get all todos responses: 200: body: text/plain: type: string ================================================ FILE: raml/src/test/resources/produces/multiple_default_media_types.raml ================================================ #%RAML 1.0 title: test api version: v3 mediaType: [application/json, application/xml] /todos: displayName: Todos get: displayName: Get all todos responses: 200: body: type: string ================================================ FILE: raml/src/test/resources/produces/multiple_method_declarations.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos responses: 200: body: application/json: type: string application/xml: type: string ================================================ FILE: raml/src/test/resources/produces/no_media_type.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos body: type: string ================================================ FILE: raml/src/test/resources/produces/single_default_media_type.raml ================================================ #%RAML 1.0 title: test api version: v3 mediaType: application/json /todos: displayName: Todos get: displayName: Get all todos responses: 200: body: type: string ================================================ FILE: raml/src/test/resources/produces/single_method_declaration.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos responses: 200: body: text/plain: type: string ================================================ FILE: raml/src/test/resources/query_parameter/query_parameter.raml ================================================ #%RAML 1.0 title: test api version: v3 /todos: displayName: Todos get: displayName: Get all todos queryParameters: tag: displayName: Tag type: string description: Filter todos by a given tag example: Mary Roach required: false limit: displayName: Limit type: number description: Limit results in response example: 25 required: true ================================================ FILE: settings.gradle ================================================ rootProject.name = 'hikaku' include( 'core', 'openapi', 'spring', 'wadl', 'raml', 'jax-rs', 'micronaut' ) ================================================ FILE: spring/README.md ================================================ # hikaku - Spring Supports Spring MVC 5.3.X. ## Feature Support Please refer to the list of [all features](../docs/features.md). To check the feature support for this converter. You will find a list of spring specific features that are supported below. ### Paths + Supports RequestMapping annotation + _Example:_ `@RequestMapping("/todos")` + Supports all HTTP method based mapping annotations (DeleteMapping, GetMapping, PatchMapping, PostMapping, PutMapping) + _Example:_ `@GetMapping("/todos")` + Supports endpoint definition on class level, method level and a combination of both. + Supports multiple path definitions on all HTTP method based mapping annotations + _Example:_ `@GetMapping(value = ["/todos", "todo/list"])` + Supports multiple path definitions on RequestMapping annotation + _Example:_ `@RequestMapping(value = ["/todos", "todo/list"], method = [RequestMethod.POST, RequestMethod.GET])` + Supports endpoints using regex for path parameter + _Example:_ `@RequestMapping("/{id:[0-9]+}")` ### HTTP method + Supports RequestMapping annotation + _Example:_ `@RequestMapping(method = GET)` + Supports all HTTP method based mapping annotations (DeleteMapping, GetMapping, PatchMapping, PostMapping, PutMapping) + _Example:_ `@GetMapping("/todos")` ### Query parameters + Supports parameter name using variable name + _Example:_ `@RequestParam tag: String` + Supports parameter name defined by 'value' + _Example:_ `@RequestParam(value = "tag") otherName: String` + Supports parameter name defined by alias 'name' + _Example:_ `@RequestParam(name = "tag") otherName: String` + Throws an exception in case both 'value' and 'name' are set. + _Example:_ `@RequestParam(value = "param", name = "other") foo: String` + Checks if a parameter is required depending on the value of the 'required' attribute + _Example:_ `@RequestParam(required = false) foo: String` + Checks if a parameter is required depending on the existence of the 'defaultValue' attribute + _Example:_ `@RequestParam(value = "tag", defaultValue = "all")` ### Path parameters + Supports parameter name using variable name + _Example:_ `@PathVariable id: Int` + Supports parameter name defined by 'value' + _Example:_ `@PathVariable(value = "id") otherName: Int` + Supports parameter name defined by alias 'name' + _Example:_ `@PathVariable(name = "id") otherName: Int` + Throws an exception in case both 'value' and 'name' are set. + _Example:_ `@PathVariable(value = "param", name = "other") foo: Int` ### Header parameters + Supports parameter name using variable name + _Example:_ `@RequestHeader allowCache: String` + Supports parameter name defined by 'value' + _Example:_ `@RequestParam(value = "allow-cache") otherName: String` + Supports parameter name defined by alias 'name' + _Example:_ `@RequestHeader(name = "allow-cache") otherName: String` + Throws an exception in case both 'value' and 'name' are set. + _Example:_ `@RequestHeader(value = "param", name = "other") foo: String` + Checks if a parameter is required depending on the value of the 'required' attribute + _Example:_ `@RequestHeader(required = false) foo: String` + Checks if a parameter is required depending on the existence of the 'defaultValue' attribute + _Example:_ `@RequestHeader(value = "tracker-id", defaultValue = "2394")` ### Matrix parameters + Supports parameter name using variable name + _Example:_ `@MatrixVariable tag: String` + Supports parameter name defined by 'value' + _Example:_ `@MatrixVariable(value = "tag") otherName: String` + Supports parameter name defined by alias 'name' + _Example:_ `@MatrixVariable(name = "tag") otherName: String` + Throws an exception in case both 'value' and 'name' are set. + _Example:_ `@MatrixVariable(value = "param", name = "other") foo: String` + Checks if a parameter is required depending on the value of the 'required' attribute + _Example:_ `@MatrixVariable(required = false) foo: String` + Checks if a parameter is required depending on the existence of the 'defaultValue' attribute + _Example:_ `@MatrixVariable(value = "tag", defaultValue = "shopping")` ### Produces + Supports RequestMapping annotation + _Example:_ `@RequestMapping(produces = "text/plain")` + Supports all HTTP method based mapping annotations (DeleteMapping, GetMapping, PatchMapping, PostMapping, PutMapping) + _Example:_ `@GetMapping(produces = "text/plain")` + Supports `javax.servlet.http.HttpServletResponse` parameter type + _Example:_ `@GetMapping("/items") fun getItems(response: HttpServletResponse)` + Supports default value in case no produces definition has been set + Supports text/plain if the return value is a String and no produces definition has been set ### Consumes + Supports RequestMapping annotation + _Example:_ `@RequestMapping(consumes = "text/plain")` + Supports all HTTP method based mapping annotations (DeleteMapping, GetMapping, PatchMapping, PostMapping, PutMapping) + _Example:_ `@GetMapping(consumes = "text/plain")` + Supports default value in case no consumes definition has been set + Supports `*/*` if the return value is a String and no consumes definition has been set ## Currently not supported + Checking whether or not to explode query parameter. + _Example:_ `@RequestParam(value="tag", required=false) tags: List` **or** `@RequestParam(value="tag", required=false) tags: Array` + Query Parameter based on an object + _Example:_ `@GetMapping("/todos") fun getAllTodos(queryParams: MyObject)` + Parameter annotations on a HashMap to dynamically extract parameters + Matrix parameters having a binding to a specific path element + _Example:_ `@MatrixVariable(pathVar = "employee")` + Produces using negated media type + _Example:_ `@RequestParam(produces = "!text/plain")` + Consumes using negated media type + _Example:_ `@RequestParam(produces = "!text/plain")` ================================================ FILE: spring/build.gradle ================================================ buildscript { ext { springBootVersion = '2.5.4' } } group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-spring' dependencies { api 'org.springframework:spring-webmvc:5.3.9' api project(':core') testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testImplementation "org.springframework.boot:spring-boot-starter-web:$springBootVersion" } uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-spring' description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains a converter for spring-mvc implementations.' } } } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/SpringConverter.kt ================================================ package de.codecentric.hikaku.converters.spring import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.AbstractEndpointConverter import de.codecentric.hikaku.converters.spring.extensions.* import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod import de.codecentric.hikaku.endpoints.HttpMethod.* import org.springframework.context.ApplicationContext import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.mvc.method.RequestMappingInfo import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping /** * Extracts and converts [Endpoint]s from a spring [ApplicationContext]. * @param applicationContext Spring application context */ class SpringConverter(private val applicationContext: ApplicationContext) : AbstractEndpointConverter() { override val supportedFeatures = SupportedFeatures( Feature.QueryParameters, Feature.PathParameters, Feature.HeaderParameters, Feature.MatrixParameters, Feature.Produces, Feature.Consumes, Feature.Deprecation ) override fun convert(): Set { return applicationContext.getBean(RequestMappingHandlerMapping::class.java) .handlerMethods .flatMap { mappingEntry -> mappingEntry.key.paths().flatMap { path -> createEndpoints(path, mappingEntry) } } .toSet() } private fun createEndpoints(path: String, mappingEntry: Map.Entry): Set { val httpMethods = extractAvailableHttpMethods(mappingEntry) val cleanedPath = removeRegex(path) val endpoints = httpMethods.map { Endpoint( path = cleanedPath, httpMethod = it, queryParameters = mappingEntry.value.hikakuQueryParameters(), pathParameters = mappingEntry.value.hikakuPathParameters(), headerParameters = mappingEntry.value.hikakuHeaderParameters(), matrixParameters = mappingEntry.value.hikakuMatrixParameters(), produces = mappingEntry.produces(), consumes = mappingEntry.consumes(), deprecated = mappingEntry.isEndpointDeprecated() ) } .toMutableSet() // Spring always adds an OPTIONS http method if it does not exist, but without query and path parameter if (!httpMethods.contains(OPTIONS)) { endpoints.add( Endpoint( path = cleanedPath, httpMethod = OPTIONS, deprecated = mappingEntry.isEndpointDeprecated() ) ) } return endpoints } private fun removeRegex(path: String): String { return path.split('/').joinToString("/") { pathSegment -> pathSegment.let { when { it.contains(':') -> it.replace(Regex(":.*"), "}") else -> it } } } } private fun extractAvailableHttpMethods(mappingEntry: Map.Entry): Set { val httpMethods = mappingEntry.key.hikakuHttpMethods() // Spring adds all http methods except for TRACE if no http method has been set explicitly // OPTIONS is a special case. If it's not added manually it has to be added without any path or query parameters return if (httpMethods.isEmpty()) { HttpMethod.values() .filterNot { it == TRACE } .filterNot { it == OPTIONS } .toSet() } else { // Spring always adds a HEAD http method httpMethods + HEAD } } companion object { @JvmField val IGNORE_ERROR_ENDPOINT: (Endpoint) -> Boolean = { endpoint -> endpoint.path == "/error" } } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/ConsumesExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import org.springframework.http.MediaType.ALL_VALUE import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.mvc.method.RequestMappingInfo import kotlin.reflect.full.findAnnotation import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.jvm.kotlinFunction internal fun Map.Entry.consumes(): Set { val consumes = this.key .consumesCondition .expressions .map { it.mediaType.toString() } .toSet() if (consumes.isNotEmpty()) { return consumes } val providesRequestBodyAnnotation = this.value .method .kotlinFunction ?.parameters ?.any { it.findAnnotation() !== null } ?: false if (!providesRequestBodyAnnotation) { return emptySet() } val isParameterString = this.value .method .kotlinFunction ?.parameters ?.firstOrNull { it.findAnnotation() !== null } ?.type ?.jvmErasure ?.let { it.java == java.lang.String::class.java } ?: false return if (isParameterString) { setOf(ALL_VALUE) } else { setOf(APPLICATION_JSON_VALUE) } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/DeprecationExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.mvc.method.RequestMappingInfo internal fun Map.Entry.isEndpointDeprecated() = this.value.method.isAnnotationPresent(Deprecated::class.java) || this.value.method.declaringClass.isAnnotationPresent(Deprecated::class.java) ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/HeaderParametersSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import de.codecentric.hikaku.endpoints.HeaderParameter import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.ValueConstants import org.springframework.web.method.HandlerMethod import kotlin.reflect.KParameter import kotlin.reflect.jvm.kotlinFunction internal fun HandlerMethod.hikakuHeaderParameters(): Set { val method = this.method.kotlinFunction ?: return emptySet() return method.parameters .filter { it.annotations.filterIsInstance().any() } .map { extractHeaderParameter(it) } .toSet() } private fun extractHeaderParameter(it: KParameter): HeaderParameter { val requestHeader = it.annotations.find { it is RequestHeader } as RequestHeader val parameterName = extractHeaderParameterName(requestHeader, it) val isRequired = isHeaderParameterRequired(requestHeader) return HeaderParameter(parameterName, isRequired) } private fun isHeaderParameterRequired(requestHeader: RequestHeader): Boolean { if (requestHeader.defaultValue == ValueConstants.DEFAULT_NONE) { return requestHeader.required } return false } private fun extractHeaderParameterName(requestHeader: RequestHeader, it: KParameter): String { check(!(requestHeader.value.isNotBlank() && requestHeader.name.isNotBlank())) { "Both 'value' and 'name' attribute are provided for header parameter '${it.name}'. Only one is permitted." } return when { requestHeader.value.isNotBlank() -> requestHeader.value requestHeader.name.isNotBlank() -> requestHeader.name else -> it.name ?: "" } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/HttpMethodsSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import de.codecentric.hikaku.endpoints.HttpMethod import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.servlet.mvc.method.RequestMappingInfo internal fun RequestMappingInfo.hikakuHttpMethods(): Set { return this.methodsCondition.methods .map { when (it) { RequestMethod.GET -> HttpMethod.GET RequestMethod.POST -> HttpMethod.POST RequestMethod.HEAD -> HttpMethod.HEAD RequestMethod.PUT -> HttpMethod.PUT RequestMethod.PATCH -> HttpMethod.PATCH RequestMethod.DELETE -> HttpMethod.DELETE RequestMethod.TRACE -> HttpMethod.TRACE RequestMethod.OPTIONS -> HttpMethod.OPTIONS null -> HttpMethod.OPTIONS } } .toSet() } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/MatrixParametersSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import de.codecentric.hikaku.endpoints.MatrixParameter import org.springframework.web.bind.annotation.MatrixVariable import org.springframework.web.bind.annotation.ValueConstants import org.springframework.web.method.HandlerMethod import kotlin.reflect.KParameter import kotlin.reflect.jvm.kotlinFunction internal fun HandlerMethod.hikakuMatrixParameters(): Set { val method = this.method.kotlinFunction ?: return emptySet() return method.parameters .filter { it.annotations.filterIsInstance().any() } .map { extractMatrixParameter(it) } .toSet() } private fun extractMatrixParameter(it: KParameter): MatrixParameter { val matrixParameter = it.annotations.find { it is MatrixVariable } as MatrixVariable val parameterName = extractMatrixParameterName(matrixParameter, it) val isRequired = isMatrixParameterRequired(matrixParameter) return MatrixParameter(parameterName, isRequired) } private fun isMatrixParameterRequired(matrixParameter: MatrixVariable): Boolean { if (matrixParameter.defaultValue == ValueConstants.DEFAULT_NONE) { return matrixParameter.required } return false } private fun extractMatrixParameterName(matrixParameter: MatrixVariable, it: KParameter): String { check(!(matrixParameter.value.isNotBlank() && matrixParameter.name.isNotBlank())) { "Both 'value' and 'name' attribute are provided for matrix parameter '${it.name}'. Only one is permitted." } return when { matrixParameter.value.isNotBlank() -> matrixParameter.value matrixParameter.name.isNotBlank() -> matrixParameter.name else -> it.name ?: "" } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/PathParametersSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import de.codecentric.hikaku.endpoints.PathParameter import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.method.HandlerMethod import kotlin.reflect.KParameter import kotlin.reflect.jvm.kotlinFunction internal fun HandlerMethod.hikakuPathParameters(): Set { val method = this.method.kotlinFunction ?: return emptySet() return method.parameters .filter { it.annotations.filterIsInstance().any() } .map { extractPathParameter(it) } .toSet() } private fun extractPathParameter(it: KParameter): PathParameter { val requestParam = it.annotations.find { it is PathVariable } as PathVariable val parameterName = extractPathParameterName(requestParam, it) return PathParameter(parameterName) } private fun extractPathParameterName(requestParam: PathVariable, it: KParameter): String { check(!(requestParam.value.isNotBlank() && requestParam.name.isNotBlank())) { "Both 'value' and 'name' attribute are provided for path parameter '${it.name}'. Only one is permitted." } return when { requestParam.value.isNotBlank() -> requestParam.value requestParam.name.isNotBlank() -> requestParam.name else -> it.name ?: "" } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/PathsSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import org.springframework.web.servlet.mvc.method.RequestMappingInfo internal fun RequestMappingInfo.paths(): Set { return this.patternsCondition?.patterns ?: emptySet() } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/ProducesSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import de.codecentric.hikaku.extensions.isString import de.codecentric.hikaku.extensions.isUnit import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.http.MediaType.TEXT_PLAIN_VALUE import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.mvc.method.RequestMappingInfo import org.springframework.web.servlet.view.RedirectView import java.lang.reflect.Method import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.instanceParameter import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.jvm.kotlinFunction internal fun Map.Entry.produces(): Set { val isNotErrorPath = this.key.patternsCondition?.patterns?.contains("/error") == false val hasNoResponseBodyAnnotation = !this.value.providesResponseBodyAnnotation() val hasNoRestControllerAnnotation = !this.value.providesRestControllerAnnotation() val hasHttpServletResponseParam = this.value.hasHttpServletResponseParam() if (isNotErrorPath && (hasNoResponseBodyAnnotation && hasNoRestControllerAnnotation)) { return emptySet() } if (isNotErrorPath && (this.value.method.hasNoReturnType() && !hasHttpServletResponseParam)) { return emptySet() } val produces = this.key .producesCondition .expressions .map { it.mediaType.toString() } .toSet() if (produces.isNotEmpty()) { return produces } val returnType = this.value .method .kotlinFunction ?.returnType ?.jvmErasure return if (returnType != null) { when { returnType.isString() -> setOf(TEXT_PLAIN_VALUE) returnType.isUnit() -> emptySet() returnType == RedirectView::class -> emptySet() else -> setOf(APPLICATION_JSON_VALUE) } } else { emptySet() } } private fun Method.hasNoReturnType() = this.returnType.kotlin.isUnit() private fun HandlerMethod.providesRestControllerAnnotation() = this.method .kotlinFunction ?.instanceParameter ?.type ?.jvmErasure ?.findAnnotation() != null private fun HandlerMethod.providesResponseBodyAnnotation() = isResponseBodyAnnotationOnClass() || isResponseBodyAnnotationOnFunction() private fun HandlerMethod.isResponseBodyAnnotationOnClass() = this.method .kotlinFunction ?.instanceParameter ?.type ?.jvmErasure ?.findAnnotation() != null private fun HandlerMethod.isResponseBodyAnnotationOnFunction() = this.method .kotlinFunction ?.findAnnotation() != null private fun HandlerMethod.hasHttpServletResponseParam(): Boolean { return this.methodParameters .any { it.parameterType.name == "javax.servlet.http.HttpServletResponse" } } ================================================ FILE: spring/src/main/kotlin/de/codecentric/hikaku/converters/spring/extensions/QueryParametersSpringExtension.kt ================================================ package de.codecentric.hikaku.converters.spring.extensions import de.codecentric.hikaku.endpoints.QueryParameter import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ValueConstants import org.springframework.web.method.HandlerMethod import kotlin.reflect.KParameter import kotlin.reflect.jvm.kotlinFunction internal fun HandlerMethod.hikakuQueryParameters(): Set { val method = this.method.kotlinFunction ?: return emptySet() return method.parameters .filter { it.annotations.filterIsInstance().any() } .map { extractQueryParameter(it) } .toSet() } private fun extractQueryParameter(it: KParameter): QueryParameter { val requestParam = it.annotations.find { it is RequestParam } as RequestParam val parameterName = extractQueryParameterName(requestParam, it) val isRequired = isQueryParameterRequired(requestParam) return QueryParameter(parameterName, isRequired) } private fun isQueryParameterRequired(requestParam: RequestParam): Boolean { if (requestParam.defaultValue == ValueConstants.DEFAULT_NONE) { return requestParam.required } return false } private fun extractQueryParameterName(requestParam: RequestParam, it: KParameter): String { check(!(requestParam.value.isNotBlank() && requestParam.name.isNotBlank())) { "Both 'value' and 'name' attribute are provided for query parameter '${it.name}'. Only one is permitted." } return when { requestParam.value.isNotBlank() -> requestParam.value requestParam.name.isNotBlank() -> requestParam.name else -> it.name ?: "" } } ================================================ FILE: spring/src/main/resources/.gitemptydir ================================================ ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/consumes/ConsumesTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.consumes import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.http.MediaType.* import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.* import org.springframework.web.multipart.MultipartFile @SpringBootApplication open class DummyApp data class Todo(val description: String) data class Tag(val name: String) @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class RequestMappingOneMediaTypeIsInheritedByAllFunctionsController { @RequestMapping("/todos") fun todos(@RequestBody todo: Todo) { } @RequestMapping("/tags") fun tags(@RequestBody tag: Tag) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) @Suppress("UNUSED_PARAMETER") open class RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController { @RequestMapping("/todos") fun todos(@RequestBody todo: Todo) { } @RequestMapping("/tags") fun tags(@RequestBody tag: Tag) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOneMediaTypeIsExtractedCorrectlyController { @RequestMapping("/todos", consumes = [APPLICATION_XML_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingMultipartFormIsExtractedCorrectlyController { @RequestMapping( path = ["/form"], method = [RequestMethod.POST], consumes = [MULTIPART_FORM_DATA_VALUE] ) fun form( @RequestPart("title") title: String, @RequestPart("file") form: MultipartFile ) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingMultipleMediaTypesAreExtractedCorrectlyController { @RequestMapping("/todos", consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController { @RequestMapping("/todos") fun todos(@RequestBody todo: Todo) { } @RequestMapping("/tags", consumes = [TEXT_PLAIN_VALUE]) fun tags(@RequestBody tag: Tag) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController { @RequestMapping("/todos") fun todos(@RequestBody todo: Todo) { } @RequestMapping("/tags", consumes = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun tags(@RequestBody tag: Tag) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class RequestMappingOnClassDefaultValueController { @RequestMapping fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnFunctionDefaultValueController { @RequestMapping("/todos") fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class RequestMappingOnClassWithoutConsumesInfoAndStringAsRequestBodyValueController { @RequestMapping fun todos(@RequestBody todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnFunctionWithoutConsumesInfoAndStringAsRequestBodyValueController { @RequestMapping("/todos") fun todos(@RequestBody todo: String) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class RequestMappingOnClassWithoutRequestBodyAnnotationController { @RequestMapping fun todos(todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnFunctionWithoutConsumesAnnotationController { @RequestMapping("/todos") fun todos(todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class GetMappingOneMediaTypeIsExtractedCorrectlyController { @GetMapping("/todos", consumes = [APPLICATION_XML_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class GetMappingMultipleMediaTypesAreExtractedCorrectlyController { @GetMapping("/todos", consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class GetMappingDefaultValueController { @GetMapping("/todos") fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class GetMappingWithoutConsumesInfoAndStringAsRequestBodyValueController { @GetMapping("/todos") fun todos(@RequestBody todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class GetMappingWithoutRequestBodyAnnotationController { @GetMapping("/todos") fun todos(todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class DeleteMappingOneMediaTypeIsExtractedCorrectlyController { @DeleteMapping("/todos", consumes = [APPLICATION_XML_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController { @DeleteMapping("/todos", consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class DeleteMappingDefaultValueController { @DeleteMapping("/todos") fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class DeleteMappingWithoutConsumesInfoAndStringAsRequestBodyValueController { @DeleteMapping("/todos") fun todos(@RequestBody todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class DeleteMappingWithoutRequestBodyAnnotationController { @DeleteMapping("/todos") fun todos(todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PatchMappingOneMediaTypeIsExtractedCorrectlyController { @PatchMapping("/todos", consumes = [APPLICATION_XML_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PatchMappingMultipleMediaTypesAreExtractedCorrectlyController { @PatchMapping("/todos", consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PatchMappingDefaultValueController { @PatchMapping("/todos") fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PatchMappingWithoutConsumesInfoAndStringAsRequestBodyValueController { @PatchMapping("/todos") fun todos(@RequestBody todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PatchMappingWithoutRequestBodyAnnotationController { @PatchMapping("/todos") fun todos(todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PostMappingOneMediaTypeIsExtractedCorrectlyController { @PostMapping("/todos", consumes = [APPLICATION_XML_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PostMappingMultipleMediaTypesAreExtractedCorrectlyController { @PostMapping("/todos", consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PostMappingDefaultValueController { @PostMapping("/todos") fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PostMappingWithoutConsumesInfoAndStringAsRequestBodyValueController { @PostMapping("/todos") fun todos(@RequestBody todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PostMappingWithoutRequestBodyAnnotationController { @PostMapping("/todos") fun todos(todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PutMappingOneMediaTypeIsExtractedCorrectlyController { @PutMapping("/todos", consumes = [APPLICATION_XML_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PutMappingMultipleMediaTypesAreExtractedCorrectlyController { @PutMapping("/todos", consumes = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PutMappingDefaultValueController { @PutMapping("/todos") fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PutMappingWithoutConsumesInfoAndStringAsRequestBodyValueController { @PutMapping("/todos") fun todos(@RequestBody todo: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PutMappingWithoutRequestBodyAnnotationController { @PutMapping("/todos") fun todos(todo: String) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class GetMappingOneMediaTypeIsOverwrittenController { @GetMapping("/todos", consumes = [TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class DeleteMappingOneMediaTypeIsOverwrittenController { @DeleteMapping("/todos", consumes = [TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class PatchMappingOneMediaTypeIsOverwrittenController { @PatchMapping("/todos", consumes = [TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class PostMappingOneMediaTypeIsOverwrittenController { @PostMapping("/todos", consumes = [TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class PutMappingOneMediaTypeIsOverwrittenController { @PutMapping("/todos", consumes = [TEXT_PLAIN_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class GetMappingMultipleMediaTypesAreOverwrittenController { @GetMapping("/todos", consumes = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class DeleteMappingMultipleMediaTypesAreOverwrittenController { @DeleteMapping("/todos", consumes = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class PatchMappingMultipleMediaTypesAreOverwrittenController { @PatchMapping("/todos", consumes = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class PostMappingMultipleMediaTypesAreOverwrittenController { @PostMapping("/todos", consumes = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @RequestMapping(consumes = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) @Suppress("UNUSED_PARAMETER") open class PutMappingMultipleMediaTypesAreOverwrittenController { @PutMapping("/todos", consumes = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos(@RequestBody todo: Todo) { } } @Controller @Suppress("UNUSED_PARAMETER") open class ErrorEndpointController { @GetMapping("/todos", consumes = [APPLICATION_PDF_VALUE]) fun todos(@RequestBody todo: Todo) { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/consumes/SpringConverterConsumesTest.kt ================================================ package de.codecentric.hikaku.converters.spring.consumes import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.* class SpringConverterConsumesTest { @Nested inner class RequestMappingAnnotationTests { @Nested inner class ClassLevelTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsInheritedByAllFunctionsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping annotation is inherited by all functions`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/tags", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/tags", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/tags", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/tags", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/tags", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/tags", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/tags", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreInheritedByAllFunctionsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at class level using RequestMapping annotation are inherited by all functions`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/tags", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/tags", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at class level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoRequestBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class FunctionLevelTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using RequestMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipartFormIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipartFormIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multipart form media type is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/form", httpMethod = POST, consumes = setOf(MULTIPART_FORM_DATA_VALUE) ), Endpoint( path = "/form", httpMethod = HEAD, consumes = setOf(MULTIPART_FORM_DATA_VALUE) ), Endpoint( path = "/form", httpMethod = OPTIONS, ), ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using RequestMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionWithoutConsumesAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class EmptyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody nor consumes annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } @Nested inner class GetMappingAnnotationTests { @Nested @WebMvcTest(GetMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using GetMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using GetMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoRequestBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class DeleteMappingAnnotationTests { @Nested @WebMvcTest(DeleteMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using DeleteMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using DeleteMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoRequestBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PatchMappingAnnotationTests { @Nested @WebMvcTest(PatchMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PatchMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PatchMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoRequestBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PostMappingAnnotationTests { @Nested @WebMvcTest(PostMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PostMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PostMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoRequestBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PutMappingAnnotationTests { @Nested @WebMvcTest(PutMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PutMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PutMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingWithoutConsumesInfoAndStringAsRequestBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to accept all`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(ALL_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(ALL_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingWithoutRequestBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoRequestBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no RequestBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class OverwriteTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by a declaration at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/tags", httpMethod = GET, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = POST, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = PUT, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = PATCH, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = DELETE, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/tags", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by a declaration at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/tags", httpMethod = GET, consumes = setOf(APPLICATION_PDF_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = POST, consumes = setOf(APPLICATION_PDF_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = HEAD, consumes = setOf(APPLICATION_PDF_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = PUT, consumes = setOf(APPLICATION_PDF_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = PATCH, consumes = setOf(APPLICATION_PDF_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/tags", httpMethod = DELETE, consumes = setOf(APPLICATION_PDF_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/tags", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by GetMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by DeleteMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PatchMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PostMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PutMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by GetMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by DeleteMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PatchMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PostMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PutMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested @WebMvcTest(ErrorEndpointController::class) inner class MediaTypeIsNotAddedToDefaultErrorEndpoint { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `error endpoint does not provide the same media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf(APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, consumes = setOf(APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/deprecation/DeprecationTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.deprecation import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController @SpringBootApplication open class DummyApp data class Todo(val description: String = "") @RestController open class NoDeprecationController { @GetMapping("/todos") fun todos() = Todo() } @RestController @Deprecated("Test") open class DeprecatedClassController { @GetMapping("/todos") fun todos() = Todo() } @RestController open class DeprecatedFunctionController { @GetMapping("/todos") @Deprecated("Test") fun todos() = Todo() } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/deprecation/SpringConverterDeprecationTest.kt ================================================ package de.codecentric.hikaku.converters.spring.deprecation import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.APPLICATION_JSON_VALUE class SpringConverterDeprecationTest { @Nested @WebMvcTest(NoDeprecationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoDeprecationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no deprecation`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE), deprecated = false ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE), deprecated = false ), Endpoint("/todos", OPTIONS, deprecated = false) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeprecatedClassController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoDeprecatedClassTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `deprecated class`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE), deprecated = true ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE), deprecated = true ), Endpoint("/todos", OPTIONS, deprecated = true) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeprecatedFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoDeprecatedFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `deprcated function`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE), deprecated = true ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE), deprecated = true ), Endpoint("/todos", OPTIONS, deprecated = true) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/headerparameters/HeaderParameterTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.headerparameters import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestHeader @SpringBootApplication open class DummyApp @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterNamedByVariableController { @GetMapping("/todos") fun getAllTodos(@RequestHeader allowCache: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterNamedByValueAttributeController { @GetMapping("/todos") fun getAllTodos(@RequestHeader(value = "allow-cache") variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterNamedByNameAttributeController { @GetMapping("/todos") fun getAllTodos(@RequestHeader(name = "allow-cache") variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterHavingBothNameAndValueAttributeController { @GetMapping("/todos") fun getAllTodos(@RequestHeader(value="valueAttribute", name = "nameAttribute") variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterOptionalController { @GetMapping("/todos") fun getAllTodos(@RequestHeader(name = "allow-cache", required = false) variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterOptionalBecauseOfDefaultValueController { @GetMapping("/todos") fun getAllTodos(@RequestHeader(name = "tracker-id", defaultValue = "unknown") variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class HeaderParameterOnDefaultErrorEndpointController { @GetMapping("/todos") fun getAllTodos(@RequestHeader(value = "allow-cache") variable: Boolean) { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/headerparameters/SpringConverterHeaderParameterTest.kt ================================================ package de.codecentric.hikaku.converters.spring.headerparameters import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HeaderParameter import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.http.MediaType.TEXT_HTML_VALUE import kotlin.test.assertFailsWith class SpringConverterHeaderParameterTest { @Nested @WebMvcTest(HeaderParameterNamedByVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class HeaderParameterNamedByVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `header parameter name defined by variable name`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allowCache", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, headerParameters = setOf( HeaderParameter("allowCache", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(HeaderParameterNamedByValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class HeaderParameterNamedByValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `header parameter name defined by attribute 'value'`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(HeaderParameterNamedByNameAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class HeaderParameterNamedByNameAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `header parameter name defined by attribute 'name'`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(HeaderParameterHavingBothNameAndValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class HeaderParameterHavingBothNameAndValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `both 'value' and 'name' attribute defined for header parameter`() { assertFailsWith { SpringConverter(context).conversionResult } } } @Nested @WebMvcTest(HeaderParameterOptionalController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class HeaderParameterOptionalTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `header parameter optional`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", false) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, headerParameters = setOf( HeaderParameter("allow-cache", false) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(HeaderParameterOptionalBecauseOfDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class HeaderParameterOptionalBecauseOfDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `header parameter optional because of a default value`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("tracker-id", false) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, headerParameters = setOf( HeaderParameter("tracker-id", false) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(HeaderParameterOnDefaultErrorEndpointController::class) inner class HeaderParameterOnDefaultErrorEndpointTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `header parameter is not available in default error endpoints`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, headerParameters = setOf( HeaderParameter("allow-cache", true) ) ), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/httpmethod/HttpMethodTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.httpmethod import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.RequestMethod.* @SpringBootApplication open class DummyApp @Controller @RequestMapping("/todos", method = [GET]) open class RequestMappingDefinedOnClassWithExplicitGetMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitGetMethodController { @RequestMapping("/todos", method = [GET]) fun todos() { } } @Controller @RequestMapping("/todos", method = [POST]) open class RequestMappingDefinedOnClassWithExplicitPostMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitPostMethodController { @RequestMapping("/todos", method = [POST]) fun todos() { } } @Controller @RequestMapping("/todos", method = [HEAD]) open class RequestMappingDefinedOnClassWithExplicitHeadMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitHeadMethodController { @RequestMapping("/todos", method = [HEAD]) fun todos() { } } @Controller @RequestMapping("/todos", method = [PUT]) open class RequestMappingDefinedOnClassWithExplicitPutMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitPutMethodController { @RequestMapping("/todos", method = [PUT]) fun todos() { } } @Controller @RequestMapping("/todos", method = [PATCH]) open class RequestMappingDefinedOnClassWithExplicitPatchMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitPatchMethodController { @RequestMapping("/todos", method = [PATCH]) fun todos() { } } @Controller @RequestMapping("/todos", method = [DELETE]) open class RequestMappingDefinedOnClassWithExplicitDeleteMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitDeleteMethodController { @RequestMapping("/todos", method = [DELETE]) fun todos() { } } @Controller @RequestMapping("/todos", method = [TRACE]) open class RequestMappingDefinedOnClassWithExplicitTraceMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitTraceMethodController { @RequestMapping("/todos", method = [TRACE]) fun todos() { } } @Controller @RequestMapping("/todos", method = [OPTIONS]) open class RequestMappingDefinedOnClassWithExplicitOptionsMethodController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionWithExplicitOptionsMethodController { @RequestMapping("/todos", method = [OPTIONS]) fun todos() { } } @Controller @RequestMapping("/todos") open class RequestMappingDefinedOnClassNoHttpMethodDefinedController { @RequestMapping fun todos() { } } @Controller open class RequestMappingDefinedOnFunctionNoHttpMethodDefinedController { @RequestMapping("/todos") fun todos() { } } @Controller @RequestMapping("/todos", method = [PUT]) open class RequestMappingDefinedOnClassAndFunctionWithDifferentHttpMethodsController { @RequestMapping(method = [PATCH]) fun todos() { } } @Controller @RequestMapping("/todos", method = [GET]) open class RequestMappingDefinedOnClassAndFunctionWithDifferentHttpMethodsAndDifferentPathsController { @RequestMapping("/{id}", method = [PUT]) fun todo() { } @RequestMapping fun todos() { } } @Controller @RequestMapping("/todos", method = [PUT, PATCH, TRACE]) open class RequestMappingDefinedOnClassWithMultipleMethodsController { @RequestMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [PUT, PATCH, TRACE]) open class RequestMappingDefinedOnFunctionWithMultipleMethodsController { @RequestMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [POST]) open class RequestMappingWithPostAndGetMappingInCombinationController { @GetMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [POST]) open class RequestMappingWithPostAndDeleteMappingInCombinationController { @DeleteMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [POST]) open class RequestMappingWithPostAndPatchMappingInCombinationController { @PatchMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [TRACE]) open class RequestMappingWithTraceAndPostMappingInCombinationController { @PostMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [POST]) open class RequestMappingWithPostAndPutMappingInCombinationController { @PutMapping fun todo() { } } @Controller @RequestMapping("/todos") open class EmptyRequestMappingAndGetMappingInCombinationController { @GetMapping fun todo() { } } @Controller @RequestMapping("/todos") open class EmptyRequestMappingAndDeleteMappingInCombinationController { @DeleteMapping fun todo() { } } @Controller @RequestMapping("/todos") open class EmptyRequestMappingAndPatchMappingInCombinationController { @PatchMapping fun todo() { } } @Controller @RequestMapping("/todos") open class EmptyRequestMappingAndPostMappingInCombinationController { @PostMapping fun todo() { } } @Controller @RequestMapping("/todos") open class EmptyRequestMappingAndPutMappingInCombinationController { @PutMapping fun todo() { } } @Controller open class RequestMappingFirstAndGetMappingBothOnFunctionController { @RequestMapping("/todos") @GetMapping fun todo() { } } @Controller open class GetMappingFirstAndRequestMappingBothOnFunctionController { @GetMapping @RequestMapping("/todos") fun todo() { } } @Controller open class RequestMappingFirstAndDeleteMappingBothOnFunctionController { @RequestMapping("/todos") @DeleteMapping fun todo() { } } @Controller open class DeleteMappingFirstAndRequestMappingBothOnFunctionController { @DeleteMapping @RequestMapping("/todos") fun todo() { } } @Controller open class RequestMappingFirstAndPatchMappingBothOnFunctionController { @RequestMapping("/todos") @PatchMapping fun todo() { } } @Controller open class PatchMappingFirstAndRequestMappingBothOnFunctionController { @PatchMapping @RequestMapping("/todos") fun todo() { } } @Controller open class RequestMappingFirstAndPostMappingBothOnFunctionController { @RequestMapping("/todos") @PostMapping fun todo() { } } @Controller open class PostMappingFirstAndRequestMappingBothOnFunctionController { @PostMapping @RequestMapping("/todos") fun todo() { } } @Controller open class RequestMappingFirstAndPutMappingBothOnFunctionController { @RequestMapping("/todos") @PutMapping fun todo() { } } @Controller open class PutMappingFirstAndRequestMappingBothOnFunctionController { @PutMapping @RequestMapping("/todos") fun todo() { } } @Controller open class GetMappingController { @GetMapping("/todos") fun todos() { } } @Controller open class DeleteMappingController { @DeleteMapping("/todos") fun todos() { } } @Controller open class PatchMappingController { @PatchMapping("/todos") fun todos() { } } @Controller open class PostMappingController { @PostMapping("/todos") fun todos() { } } @Controller open class PutMappingController { @PutMapping("/todos") fun todos() { } } @Controller open class MultipleHttpMethodMappingAnnotationsController { @GetMapping("/todos") @PostMapping("/todos") @DeleteMapping("/todos") fun tdos() { } } @Controller open class HttpMethodsForDefaultErrorEndpointController { @GetMapping("/todos") fun todos() { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/httpmethod/SpringConverterHttpMethodTest.kt ================================================ package de.codecentric.hikaku.converters.spring.httpmethod import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.http.MediaType.TEXT_HTML_VALUE class HttpMethodTestController { @Nested inner class RequestMappingTests { @Nested inner class ClassLevelTests { @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitGetMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitGetMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit GET method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitPostMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitPostMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit POST method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitHeadMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitHeadMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit HEAD method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitPutMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitPutMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit PUT method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitPatchMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitPatchMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit PATCH method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitDeleteMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitDeleteMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit DELETE method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitTraceMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitTraceMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit TRACE method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", TRACE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithExplicitOptionsMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithExplicitOptionsMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit OPTIONS method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassNoHttpMethodDefinedController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoHttpMethodDefinedTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no http method defined means that spring supports all http methods except for trace`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassWithMultipleMethodsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithMultipleMethodsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint providing multiple http methods`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", TRACE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class FunctionLevelTests { @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitGetMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitGetMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit GET method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitPostMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitPostMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit POST method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitHeadMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitHeadMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit HEAD method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitPutMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitPutMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit PUT method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitPatchMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitPatchMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit PATCH method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitDeleteMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitDeleteMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit DELETE method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitTraceMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitTraceMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit TRACE method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", TRACE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithExplicitOptionsMethodController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionWithExplicitOptionsMethodTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on class having explicit OPTIONS method definition is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionNoHttpMethodDefinedController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoHttpMethodDefinedTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no http method defined means that spring supports all http methods except for trace`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnFunctionWithMultipleMethodsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassWithMultipleMethodsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint providing multiple http methods`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", TRACE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } @Nested inner class HttpMethodMappingAnnotationTests { @Nested @WebMvcTest(GetMappingController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingControllerTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on function using GetMapping anbnotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingControllerTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on function using DeleteMapping anbnotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingControllerTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on function using PatchMapping anbnotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingControllerTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on function using PostMapping anbnotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingControllerTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint defined on function using PutMapping anbnotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested inner class HttpMethodMappingAnnotationTests { @Nested @WebMvcTest(MultipleHttpMethodMappingAnnotationsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleHttpMethodMappingAnnotationsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple HttpMethodMapping annotations are not supported by spring - most outer annotation wins`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } @Nested inner class ConjunctionTests { @Nested @WebMvcTest(RequestMappingDefinedOnClassAndFunctionWithDifferentHttpMethodsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassAndFunctionWithDifferentHttpMethodsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `No overwrite for RequestMapping defined on class and function for the same path with different http methods - instead both http methods will be supported`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingDefinedOnClassAndFunctionWithDifferentHttpMethodsAndDifferentPathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassAndFunctionWithDifferentHttpMethodsAndDifferentPathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping defined on class and function for the different paths with different http methods will create two endpoints with combined http methods for the nested path`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todos/{id}", GET), Endpoint("/todos/{id}", PUT), Endpoint("/todos/{id}", OPTIONS), Endpoint("/todos/{id}", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithPostAndGetMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingWithPostAndGetMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping providing POST defined on class and GetMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithPostAndDeleteMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingWithPostAndDeleteMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping providing POST defined on class and DeleteMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithPostAndPatchMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingWithPostAndPatchMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping providing POST defined on class and PatchMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithTraceAndPostMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingWithTraceAndPostMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping providing TRACE defined on class and PostMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", TRACE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithPostAndPutMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingWithPostAndPutMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping providing POST defined on class and PutMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(EmptyRequestMappingAndGetMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class EmptyRequestMappingAndGetMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping without http method defined on class and GetMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(EmptyRequestMappingAndDeleteMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class EmptyRequestMappingAndDeleteMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping without http method defined on class and DeleteMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(EmptyRequestMappingAndPatchMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class EmptyRequestMappingAndPatchMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping without http method defined on class and PatchMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(EmptyRequestMappingAndPostMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class EmptyRequestMappingAndPostMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping without http method defined on class and PostMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(EmptyRequestMappingAndPutMappingInCombinationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class EmptyRequestMappingAndPutMappingInCombinationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping without http method defined on class and PutMapping on function`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class OverwriteTests { @Nested @WebMvcTest(RequestMappingFirstAndGetMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingFirstAndGetMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and GetMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingFirstAndRequestMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingFirstAndRequestMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and GetMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingFirstAndDeleteMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingFirstAndDeleteMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and DeleteMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingFirstAndRequestMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingFirstAndRequestMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and DeleteMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingFirstAndPatchMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingFirstAndPatchMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and PatchMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingFirstAndRequestMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingFirstAndRequestMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and PatchMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingFirstAndPostMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingFirstAndPostMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and PostMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingFirstAndRequestMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingFirstAndRequestMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and PostMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingFirstAndPutMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingFirstAndPutMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and PutMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingFirstAndRequestMappingBothOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingFirstAndRequestMappingBothOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `RequestMapping and PutMapping both on function, but behaves like RequestMapping without http methods`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested @WebMvcTest(HttpMethodsForDefaultErrorEndpointController::class) inner class HttpMethodsForDefaultErrorEndpointTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `all http methods are provided by the default error endpoint`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/matrixparameters/MatrixParameterTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.matrixparameters import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.MatrixVariable @SpringBootApplication open class DummyApp @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterNamedByVariableController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable tag: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterNamedByValueAttributeController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable(value = "tag") variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterNamedByNameAttributeController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable(name = "tag") variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterHavingBothNameAndValueAttributeController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable(value="valueAttribute", name = "nameAttribute") variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterOptionalController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable(name = "tag", required = false) variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterOptionalBecauseOfDefaultValueController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable(name = "tag", defaultValue = "unknown") variable: Boolean) { } } @Controller @Suppress("UNUSED_PARAMETER") open class MatrixParameterOnDefaultErrorEndpointController { @GetMapping("/todos") fun getAllTodos(@MatrixVariable(value = "tag") variable: Boolean) { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/matrixparameters/SpringConverterMatrixParameterTest.kt ================================================ package de.codecentric.hikaku.converters.spring.matrixparameters import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import de.codecentric.hikaku.endpoints.MatrixParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.http.MediaType.TEXT_HTML_VALUE import kotlin.test.assertFailsWith class SpringConverterMatrixParameterTest { @Nested @WebMvcTest(MatrixParameterNamedByVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MatrixParameterNamedByVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `matrix parameter name defined by variable name`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, matrixParameters = setOf( MatrixParameter("tag", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(MatrixParameterNamedByValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MatrixParameterNamedByValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `matrix parameter name defined by attribute 'value'`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, matrixParameters = setOf( MatrixParameter("tag", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(MatrixParameterNamedByNameAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MatrixParameterNamedByNameAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `matrix parameter name defined by attribute 'name'`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, matrixParameters = setOf( MatrixParameter("tag", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(MatrixParameterHavingBothNameAndValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MatrixParameterHavingBothNameAndValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `both 'value' and 'name' attribute defined for matrix parameter`() { assertFailsWith { SpringConverter(context).conversionResult } } } @Nested @WebMvcTest(MatrixParameterOptionalController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MatrixParameterOptionalTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `matrix parameter optional`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", false) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, matrixParameters = setOf( MatrixParameter("tag", false) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(MatrixParameterOptionalBecauseOfDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MatrixParameterOptionalBecauseOfDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `matrix parameter optional because of a default value`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", false) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, matrixParameters = setOf( MatrixParameter("tag", false) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(MatrixParameterOnDefaultErrorEndpointController::class) inner class MatrixParameterOnDefaultErrorEndpointTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `matrix parameter is not available in default error endpoints`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, matrixParameters = setOf( MatrixParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, matrixParameters = setOf( MatrixParameter("tag", true) ) ), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/path/PathTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.path import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.RequestMethod.GET @SpringBootApplication open class DummyApp @Controller @RequestMapping("/todos", "/todo/list", method = [GET]) open class RequestMappingOnClassWithMultiplePathsController { @RequestMapping fun todo() { } } @Controller @RequestMapping("/todos", method = [GET]) open class RequestMappingIgnoreErrorPathController { @RequestMapping fun todo() { } } @Controller @RequestMapping("/todos/{id:[0-9]+}", method = [GET]) @Suppress("UNUSED_PARAMETER") open class RequestMappingOnClassProvidingRegexForPathVariableController { @RequestMapping fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller @RequestMapping("/todos/{id:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}", method = [GET]) @Suppress("UNUSED_PARAMETER") open class RequestMappingOnClassProvidingComplexRegexForPathVariableController { @RequestMapping fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnClassProvidingMultipleRegexForPathVariableController { @RequestMapping("/todos/{id:[0-9]+}/{title:[a-z]*}", method = [GET]) fun todo(@PathVariable(name = "id",) variable: Int, @PathVariable(name = "title") title: String) { } } @Controller open class RequestMappingOnFunctionWithMultiplePathsController { @RequestMapping("/todos", "/todo/list", method = [GET]) fun todo() { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnFunctionProvidingRegexForPathVariableController { @RequestMapping("/todos/{id:[0-9]+}", method = [GET]) fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnFunctionProvidingComplexRegexForPathVariableController { @RequestMapping("/todos/{id:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}", method = [GET]) fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller @Suppress("UNUSED_PARAMETER") open class RequestMappingOnFunctionProvidingMultipleRegexForPathVariableController { @RequestMapping("/todos/{id:[0-9]+}/{title:[a-z]*}", method = [GET]) fun todo(@PathVariable(name = "id") variable: Int, @PathVariable(name = "title") title: String) { } } @Controller open class GetMappingOnFunctionWithMultiplePathsController { @GetMapping("/todos", "/todo/list") fun todo() { } } @Controller @Suppress("UNUSED_PARAMETER") open class GetMappingProvidingRegexForPathVariableController { @GetMapping("/todos/{id:[0-9]+}") fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller open class DeleteMappingOnFunctionWithMultiplePathsController { @DeleteMapping("/todos", "/todo/list") fun todo() { } } @Controller @Suppress("UNUSED_PARAMETER") open class DeleteMappingProvidingRegexForPathVariableController { @DeleteMapping("/todos/{id:[0-9]+}") fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller open class PatchMappingOnFunctionWithMultiplePathsController { @PatchMapping("/todos", "/todo/list") fun todo() { } } @Controller @Suppress("UNUSED_PARAMETER") open class PatchMappingProvidingRegexForPathVariableController { @PatchMapping("/todos/{id:[0-9]+}") fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller open class PostMappingOnFunctionWithMultiplePathsController { @PostMapping("/todos", "/todo/list") fun todo() { } } @Controller @Suppress("UNUSED_PARAMETER") open class PostMappingProvidingRegexForPathVariableController { @PostMapping("/todos/{id:[0-9]+}") fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller open class PutMappingOnFunctionWithMultiplePathsController { @PutMapping("/todos", "/todo/list") fun todo() { } } @Controller @Suppress("UNUSED_PARAMETER") open class PutMappingProvidingRegexForPathVariableController { @PutMapping("/todos/{id:[0-9]+}") fun todo(@PathVariable(name = "id") variable: Int) { } } @Controller @RequestMapping("/todo", method = [GET]) open class RequestMappingNestedPathController { @RequestMapping("/list") fun todo() { } } @Controller @RequestMapping("/todo") open class GetMappingNestedPathController { @GetMapping("/list") fun todo() { } } @Controller @RequestMapping("/todo") open class DeleteMappingNestedPathController { @DeleteMapping("/list") fun todo() { } } @Controller @RequestMapping("/todo") open class PatchMappingNestedPathController { @PatchMapping("/list") fun todo() { } } @Controller @RequestMapping("/todo") open class PostMappingNestedPathController { @PostMapping("/list") fun todo() { } } @Controller @RequestMapping("/todo") open class PutMappingNestedPathController { @PutMapping("/list") fun todo() { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/path/SpringConverterPathTest.kt ================================================ package de.codecentric.hikaku.converters.spring.path import de.codecentric.hikaku.Hikaku import de.codecentric.hikaku.HikakuConfig import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.converters.EndpointConverter import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.converters.spring.SpringConverter.Companion.IGNORE_ERROR_ENDPOINT import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext class SpringConverterPathTest { @Nested inner class RequestMappingTests { @Nested @WebMvcTest(RequestMappingIgnoreErrorPathController::class) inner class RequestMappingIgnoreErrorPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `pre defined filter for ignoring error paths`() { //given val specification = object : EndpointConverter { override val conversionResult = setOf( Endpoint(httpMethod = GET, path = "/todos"), Endpoint(httpMethod = HEAD, path = "/todos"), Endpoint(httpMethod = OPTIONS, path = "/todos") ) override val supportedFeatures = SupportedFeatures() } val hikakuConfig = HikakuConfig(filters = listOf(IGNORE_ERROR_ENDPOINT)) val implementation = SpringConverter(context) //when val hikaku = Hikaku(config = hikakuConfig, specification = specification, implementation = implementation) //then hikaku.match() } } @Nested inner class ClassLevelTests { @Nested @WebMvcTest(RequestMappingOnClassWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnClassWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", GET), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnClassProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using RequestMapping converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassProvidingComplexRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnClassProvidingComplexRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having complex regex on path parameter using RequestMapping converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassProvidingMultipleRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnClassProvidingMutlipleRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having complex regex on path parameter using RequestMapping converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}/{title}", httpMethod = GET, pathParameters = setOf( PathParameter("id"), PathParameter("title"), ) ), Endpoint( path = "/todos/{id}/{title}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id"), PathParameter("title"), ) ), Endpoint("/todos/{id}/{title}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class FunctionLevelTest { @Nested @WebMvcTest(RequestMappingOnFunctionWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnFunctionWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", GET), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnFunctionProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using RequestMapping converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionProvidingComplexRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnFunctionProvidingComplexRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having complex regex on path parameter using RequestMapping converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionProvidingMultipleRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOnFunctionProvidingMutlipleRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having complex regex on path parameter using RequestMapping converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}/{title}", httpMethod = GET, pathParameters = setOf( PathParameter("id"), PathParameter("title"), ) ), Endpoint( path = "/todos/{id}/{title}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id"), PathParameter("title"), ) ), Endpoint("/todos/{id}/{title}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } @Nested inner class HttpMethodMappingAnnotationTests { @Nested @WebMvcTest(GetMappingOnFunctionWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingOnFunctionWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", GET), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingOnFunctionWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingOnFunctionWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", DELETE), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = DELETE, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingOnFunctionWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingOnFunctionWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", PATCH), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = PATCH, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingOnFunctionWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingOnFunctionWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", POST), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = POST, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingOnFunctionWithMultiplePathsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingOnFunctionWithMultiplePathsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", OPTIONS), Endpoint("/todos", HEAD), Endpoint("/todo/list", PUT), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingProvidingRegexForPathVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingProvidingRegexForPathVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `endpoint having regex on path parameter using converts to a path without the regex`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = PUT, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class ConjunctionTests { @Nested @WebMvcTest(RequestMappingNestedPathController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingNestedPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `nested paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todo/list", GET), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingNestedPathController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingNestedPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todo/list", GET), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingNestedPathController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingNestedPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todo/list", DELETE), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingNestedPathController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingNestedPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todo/list", PATCH), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingNestedPathController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingNestedPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todo/list", POST), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingNestedPathController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingNestedPathTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple paths extracted correctly`() { //given val specification: Set = setOf( Endpoint("/todo/list", PUT), Endpoint("/todo/list", OPTIONS), Endpoint("/todo/list", HEAD) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/pathparameters/PathParameterTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.pathparameters import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMethod.OPTIONS @SpringBootApplication open class DummyApp @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class PathParameterNamedByVariableController { @GetMapping("/{id}") fun getSpecificTodoItem(@PathVariable id: Int) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class PathParameterNamedByValueAttributeController { @GetMapping("/{id}") fun getSpecificTodoItem(@PathVariable(value = "id") variable: Int) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class PathParameterNamedByNameAttributeController { @GetMapping("/{id}") fun getSpecificTodoItem(@PathVariable(name = "id") variable: Int) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class PathParameterHavingBothValueAndNameAttributeController { @GetMapping("/{id}") fun getSpecificTodoItem(@PathVariable(value = "valueAttribute", name = "nameAttribute") variable: Int) { } } @Controller @Suppress("UNUSED_PARAMETER") open class PathParameterSupportedForOptionsIfExplicitlyDefinedController { @RequestMapping("/todos/{id}", method = [OPTIONS]) fun getSpecificTodoItem(@PathVariable id: Int) { } } @Controller @RequestMapping("/todos") @Suppress("UNUSED_PARAMETER") open class PathParameterOnDefaultErrorEndpointController { @GetMapping("/{id}") fun getSpecificTodoItem(@PathVariable(value = "id") variable: Int) { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/pathparameters/SpringConverterPathParameterTest.kt ================================================ package de.codecentric.hikaku.converters.spring.pathparameters import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.http.MediaType.TEXT_HTML_VALUE import kotlin.test.assertFailsWith class SpringConverterPathParameterTest { @Nested @WebMvcTest(PathParameterNamedByVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PathParameterNamedByVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `path parameter name defined by variable name`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PathParameterNamedByValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PathParameterNamedByValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `path parameter name defined by 'value' attribute`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PathParameterNamedByNameAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PathParameterNamedByNameAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `path parameter name defined by 'name' attribute`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PathParameterHavingBothValueAndNameAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PathParameterHavingBothValueAndNameAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `path parameter name defined by both 'value' and 'name' attribute`() { assertFailsWith { SpringConverter(context).conversionResult } } } @Nested @WebMvcTest(PathParameterSupportedForOptionsIfExplicitlyDefinedController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PathParameterSupportedForOptionsIfExplicitlyDefinedTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `path parameter are supported for OPTIONS if defined explicitly`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = OPTIONS, pathParameters = setOf( PathParameter("id") ) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PathParameterOnDefaultErrorEndpointController::class) inner class PathParameterOnDefaultErrorEndpointTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `path parameters are not added to default error endpoint`() { //given val specification: Set = setOf( Endpoint( path = "/todos/{id}", httpMethod = GET, pathParameters = setOf( PathParameter("id") ) ), Endpoint("/todos/{id}", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = HEAD, pathParameters = setOf( PathParameter("id") ) ), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/redirect/RedirectTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.redirect import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController import org.springframework.web.servlet.view.RedirectView import javax.servlet.http.HttpServletResponse @SpringBootApplication open class DummyApp @RestController open class RedirectTestController { @GetMapping("/todos") fun todos(): RedirectView = RedirectView() } @RestController open class RedirectUsingHttpServletResponseTestController { @GetMapping("/todos") @ResponseBody fun getTest(response: HttpServletResponse) { response.sendRedirect("http://localhost:8080/other") } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/redirect/SpringControllerRedirectTest.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.redirect import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext class SpringControllerRedirectTest { @Nested @WebMvcTest(RedirectTestController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class ReturnsRedirectViewTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `produces not set if the return type is RedirectView`() { //given val specification: Set = setOf( Endpoint("/todos", GET, produces = emptySet()), Endpoint("/todos", HEAD, produces = emptySet()), Endpoint("/todos", OPTIONS, produces = emptySet()) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RedirectUsingHttpServletResponseTestController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RedirectUsingHttpServletResponseTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `produces is empty if there is no return type, because redirect is done using HttpServletResponse`() { //given val specification: Set = setOf( Endpoint("/todos", GET, produces = emptySet()), Endpoint("/todos", HEAD, produces = emptySet()), Endpoint("/todos", OPTIONS, produces = emptySet()) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/responsebody/ProducesResponseBodyAnnotationTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.responsebody import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.http.MediaType.* import org.springframework.http.ResponseEntity import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.* @SpringBootApplication open class DummyApp data class Todo(val description: String = "") @Controller @ResponseBody @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE]) open class RequestMappingOneMediaTypeIsInheritedByAllFunctionsController { @RequestMapping fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController()) } @Controller @ResponseBody @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) open class RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController { @RequestMapping fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController()) } @Controller @ResponseBody @RequestMapping("/todos") open class RequestMappingOnClassDefaultValueController { @RequestMapping fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOnClassDefaultValueController()) } @Controller @ResponseBody @RequestMapping("/todos") open class RequestMappingWithoutProducesOnClassInfoAndStringAsResponseBodyValueController { @RequestMapping fun getAllTodos() = "" } @Controller @RequestMapping("/todos") open class RequestMappingOnClassWithoutResponseBodyAnnotationController { @RequestMapping fun getAllTodos() { } } @RestController @RequestMapping("/todos") open class RequestMappingOnClassNoProducesInfoAndNoReturnTypeController { @RequestMapping fun todos() { } } @Controller @ResponseBody open class RequestMappingOneMediaTypeIsExtractedCorrectlyController { @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsExtractedCorrectlyController()) } @Controller @ResponseBody open class RequestMappingMultipleMediaTypesAreExtractedCorrectlyController { @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @Controller @ResponseBody open class RequestMappingOnFunctionDefaultValueController { @RequestMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOnFunctionDefaultValueController()) } @Controller @ResponseBody open class RequestMappingWithoutProducesOnFunctionInfoAndStringAsResponseBodyValueController { @RequestMapping("/todos") fun getAllTodos() = "" } @Controller open class RequestMappingOnFunctionWithoutResponseBodyAnnotationController { @RequestMapping("/todos") fun getAllTodos() { } } @RestController @RequestMapping("/todos") open class RequestMappingOnFunctionNoProducesInfoAndNoReturnTypeController { @RequestMapping fun todos() { } } @Controller @ResponseBody open class GetMappingOneMediaTypeIsExtractedCorrectlyController { @GetMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(GetMappingOneMediaTypeIsExtractedCorrectlyController()) } @Controller @ResponseBody open class GetMappingMultipleMediaTypesAreExtractedCorrectlyController { @GetMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(GetMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @Controller @ResponseBody open class GetMappingDefaultValueController { @GetMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(GetMappingDefaultValueController()) } @Controller @ResponseBody open class GetMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @GetMapping("/todos") fun getAllTodos() = "" } @Controller open class GetMappingWithoutResponseBodyAnnotationController { @GetMapping("/todos") fun getAllTodos() { } } @RestController open class GetMappingNoProducesInfoAndNoReturnTypeController { @GetMapping("/todos") fun todos() { } } @Controller @ResponseBody open class DeleteMappingOneMediaTypeIsExtractedCorrectlyController { @DeleteMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(DeleteMappingOneMediaTypeIsExtractedCorrectlyController()) } @Controller @ResponseBody open class DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController { @DeleteMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @Controller @ResponseBody open class DeleteMappingDefaultValueController { @DeleteMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(DeleteMappingDefaultValueController()) } @Controller @ResponseBody open class DeleteMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @DeleteMapping("/todos") fun getAllTodos() = "" } @Controller open class DeleteMappingWithoutResponseBodyAnnotationController { @DeleteMapping("/todos") fun getAllTodos() { } } @RestController open class DeleteMappingNoProducesInfoAndNoReturnTypeController { @DeleteMapping("/todos") fun todos() { } } @Controller @ResponseBody open class PatchMappingOneMediaTypeIsExtractedCorrectlyController { @PatchMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PatchMappingOneMediaTypeIsExtractedCorrectlyController()) } @Controller @ResponseBody open class PatchMappingMultipleMediaTypesAreExtractedCorrectlyController { @PatchMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PatchMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @Controller @ResponseBody open class PatchMappingDefaultValueController { @PatchMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(PatchMappingDefaultValueController()) } @Controller @ResponseBody open class PatchMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @PatchMapping("/todos") fun getAllTodos() = "" } @Controller open class PatchMappingWithoutResponseBodyAnnotationController { @PatchMapping("/todos") fun getAllTodos() { } } @RestController open class PatchMappingNoProducesInfoAndNoReturnTypeController { @PatchMapping("/todos") fun todos() { } } @Controller @ResponseBody open class PostMappingOneMediaTypeIsExtractedCorrectlyController { @PostMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PostMappingOneMediaTypeIsExtractedCorrectlyController()) } @Controller @ResponseBody open class PostMappingMultipleMediaTypesAreExtractedCorrectlyController { @PostMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PostMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @Controller @ResponseBody open class PostMappingDefaultValueController { @PostMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(PostMappingDefaultValueController()) } @Controller open class PostMappingWithoutResponseBodyAnnotationController { @PostMapping("/todos") fun getAllTodos() { } } @Controller @ResponseBody open class PostMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @PostMapping("/todos") fun getAllTodos() = "" } @RestController open class PostMappingNoProducesInfoAndNoReturnTypeController { @PostMapping("/todos") fun todos() { } } @Controller @ResponseBody open class PutMappingOneMediaTypeIsExtractedCorrectlyController { @PutMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PutMappingOneMediaTypeIsExtractedCorrectlyController()) } @Controller @ResponseBody open class PutMappingMultipleMediaTypesAreExtractedCorrectlyController { @PutMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PutMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @Controller @ResponseBody open class PutMappingDefaultValueController { @PutMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(PutMappingDefaultValueController()) } @Controller @ResponseBody open class PutMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @PutMapping("/todos") fun getAllTodos() = "" } @Controller open class PutMappingWithoutResponseBodyAnnotationController { @PutMapping("/todos") fun getAllTodos() { } } @RestController open class PutMappingNoProducesInfoAndNoReturnTypeController { @PutMapping("/todos") fun todos() { } } @Controller @ResponseBody @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE]) open class RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController { @RequestMapping(produces = [TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController()) } @Controller @ResponseBody @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController { @RequestMapping(produces = [TEXT_PLAIN_VALUE, "application/pdf"]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController()) } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class GetMappingOneMediaTypeIsOverwrittenController { @GetMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class DeleteMappingOneMediaTypeIsOverwrittenController { @DeleteMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class PatchMappingOneMediaTypeIsOverwrittenController { @PatchMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class PostMappingOneMediaTypeIsOverwrittenController { @PostMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class PutMappingOneMediaTypeIsOverwrittenController { @PutMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class GetMappingMultipleMediaTypesAreOverwrittenController { @GetMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class DeleteMappingMultipleMediaTypesAreOverwrittenController { @DeleteMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class PatchMappingMultipleMediaTypesAreOverwrittenController { @PatchMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class PostMappingMultipleMediaTypesAreOverwrittenController { @PostMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @Controller @ResponseBody @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class PutMappingMultipleMediaTypesAreOverwrittenController { @PutMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @Controller @ResponseBody open class ErrorEndpointController { @GetMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(ErrorEndpointController()) } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/responsebody/SpringConverterProducesResponseBodyAnnotationTest.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.responsebody import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.* class SpringConverterProducesResponseBodyAnnotationTest { @Nested inner class RequestMappingAnnotationTests { @Nested inner class ClassLevelTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsInheritedByAllFunctionsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping annotation is inherited by all functions`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreInheritedByAllFunctionsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at class level using RequestMapping annotation are inherited by all functions`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at class level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithoutProducesOnClassInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class FunctionLevelTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using RequestMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using RequestMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithoutProducesOnFunctionInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } @Nested inner class GetMappingAnnotationTests { @Nested @WebMvcTest(GetMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using GetMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using GetMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class DeleteMappingAnnotationTests { @Nested @WebMvcTest(DeleteMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using DeleteMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using DeleteMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PatchMappingAnnotationTests { @Nested @WebMvcTest(PatchMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PatchMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PatchMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PostMappingAnnotationTests { @Nested @WebMvcTest(PostMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PostMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PostMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PutMappingAnnotationTests { @Nested @WebMvcTest(PutMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PutMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PutMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingWithoutResponseBodyAnnotationController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoResponseBodyAnnotationTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no ResponseBody annotation results in an empty produces list`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class OverwriteTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by a declaration at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by a declaration at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by GetMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by DeleteMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PatchMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PostMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PutMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by GetMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by DeleteMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PatchMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PostMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PutMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested @WebMvcTest(ErrorEndpointController::class) inner class MediaTypeIsNotAddedToDefaultErrorEndpoint { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `error endpoint does not provide the same media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/restcontroller/ProducesRestControllerAnnotationTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.restcontroller import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.http.MediaType.* import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* @SpringBootApplication open class DummyApp data class Todo(val description: String = "") @RestController @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE]) open class RequestMappingOneMediaTypeIsInheritedByAllFunctionsController { @RequestMapping fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController()) } @RestController @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) open class RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController { @RequestMapping fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController()) } @RestController @RequestMapping("/todos") open class RequestMappingOnClassDefaultValueController { @RequestMapping fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOnClassDefaultValueController()) } @RestController @RequestMapping("/todos") open class RequestMappingWithoutProducesOnClassInfoAndStringAsResponseBodyValueController { @RequestMapping fun getAllTodos() = "" } @RestController @RequestMapping("/todos") open class RequestMappingOnClassNoProducesInfoAndNoReturnTypeController { @RequestMapping fun todos() { } } @RestController open class RequestMappingOneMediaTypeIsExtractedCorrectlyController { @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsExtractedCorrectlyController()) } @RestController open class RequestMappingMultipleMediaTypesAreExtractedCorrectlyController { @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @RestController open class RequestMappingOnFunctionDefaultValueController { @RequestMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOnFunctionDefaultValueController()) } @RestController open class RequestMappingWithoutProducesOnFunctionInfoAndStringAsResponseBodyValueController { @RequestMapping("/todos") fun getAllTodos() = "" } @RestController @RequestMapping("/todos") open class RequestMappingOnFunctionNoProducesInfoAndNoReturnTypeController { @RequestMapping fun todos() { } } @RestController open class GetMappingOneMediaTypeIsExtractedCorrectlyController { @GetMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(GetMappingOneMediaTypeIsExtractedCorrectlyController()) } @RestController open class GetMappingMultipleMediaTypesAreExtractedCorrectlyController { @GetMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(GetMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @RestController open class GetMappingDefaultValueController { @GetMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(GetMappingDefaultValueController()) } @RestController open class GetMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @GetMapping("/todos") fun getAllTodos() = "" } @RestController open class GetMappingNoProducesInfoAndNoReturnTypeController { @GetMapping("/todos") fun todos() { } } @RestController open class DeleteMappingOneMediaTypeIsExtractedCorrectlyController { @DeleteMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(DeleteMappingOneMediaTypeIsExtractedCorrectlyController()) } @RestController open class DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController { @DeleteMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @RestController open class DeleteMappingDefaultValueController { @DeleteMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(DeleteMappingDefaultValueController()) } @RestController open class DeleteMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @DeleteMapping("/todos") fun getAllTodos() = "" } @RestController open class DeleteMappingNoProducesInfoAndNoReturnTypeController { @DeleteMapping("/todos") fun todos() { } } @RestController open class PatchMappingOneMediaTypeIsExtractedCorrectlyController { @PatchMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PatchMappingOneMediaTypeIsExtractedCorrectlyController()) } @RestController open class PatchMappingMultipleMediaTypesAreExtractedCorrectlyController { @PatchMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PatchMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @RestController open class PatchMappingDefaultValueController { @PatchMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(PatchMappingDefaultValueController()) } @RestController open class PatchMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @PatchMapping("/todos") fun getAllTodos() = "" } @RestController open class PatchMappingNoProducesInfoAndNoReturnTypeController { @PatchMapping("/todos") fun todos() { } } @RestController open class PostMappingOneMediaTypeIsExtractedCorrectlyController { @PostMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PostMappingOneMediaTypeIsExtractedCorrectlyController()) } @RestController open class PostMappingMultipleMediaTypesAreExtractedCorrectlyController { @PostMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PostMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @RestController open class PostMappingDefaultValueController { @PostMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(PostMappingDefaultValueController()) } @RestController open class PostMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @PostMapping("/todos") fun getAllTodos() = "" } @RestController open class PostMappingNoProducesInfoAndNoReturnTypeController { @PostMapping("/todos") fun todos() { } } @RestController open class PutMappingOneMediaTypeIsExtractedCorrectlyController { @PutMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PutMappingOneMediaTypeIsExtractedCorrectlyController()) } @RestController open class PutMappingMultipleMediaTypesAreExtractedCorrectlyController { @PutMapping("/todos", produces = [APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(PutMappingMultipleMediaTypesAreExtractedCorrectlyController()) } @RestController open class PutMappingDefaultValueController { @PutMapping("/todos") fun getAllTodos() = ResponseEntity.status(200).body(PutMappingDefaultValueController()) } @RestController open class PutMappingWithoutProducesInfoAndStringAsResponseBodyValueController { @PutMapping("/todos") fun getAllTodos() = "" } @RestController open class PutMappingNoProducesInfoAndNoReturnTypeController { @PutMapping("/todos") fun todos() { } } @RestController @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE]) open class RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController { @RequestMapping(produces = [TEXT_PLAIN_VALUE]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController()) } @RestController @RequestMapping("/todos", produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController { @RequestMapping(produces = [TEXT_PLAIN_VALUE, "application/pdf"]) fun getAllTodos() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController()) @RequestMapping("/{id}") fun getSpecificTodo() = ResponseEntity.status(200).body(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController()) } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class GetMappingOneMediaTypeIsOverwrittenController { @GetMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class DeleteMappingOneMediaTypeIsOverwrittenController { @DeleteMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class PatchMappingOneMediaTypeIsOverwrittenController { @PatchMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class PostMappingOneMediaTypeIsOverwrittenController { @PostMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE]) open class PutMappingOneMediaTypeIsOverwrittenController { @PutMapping("/todos", produces = [TEXT_PLAIN_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class GetMappingMultipleMediaTypesAreOverwrittenController { @GetMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class DeleteMappingMultipleMediaTypesAreOverwrittenController { @DeleteMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class PatchMappingMultipleMediaTypesAreOverwrittenController { @PatchMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class PostMappingMultipleMediaTypesAreOverwrittenController { @PostMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @RestController @RequestMapping(produces = [APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE]) open class PutMappingMultipleMediaTypesAreOverwrittenController { @PutMapping("/todos", produces = [TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE]) fun todos() = Todo() } @RestController open class ErrorEndpointController { @GetMapping("/todos", produces = [APPLICATION_XML_VALUE]) fun todos() = ResponseEntity.status(200).body(ErrorEndpointController()) } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/restcontroller/SpringConverterProducesRestControllerAnnotationTest.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.restcontroller import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType.* class SpringConverterProducesRestControllerAnnotationTest { @Nested inner class RequestMappingAnnotationTests { @Nested inner class ClassLevelTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsInheritedByAllFunctionsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping annotation is inherited by all functions`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreInheritedByAllFunctionsController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreInheritedByAllFunctionsTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at class level using RequestMapping annotation are inherited by all functions`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnClassUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at class level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithoutProducesOnClassInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnClassNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class FunctionLevelTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using RequestMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using RequestMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingWithoutProducesOnFunctionInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingOnFunctionNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", PUT), Endpoint("/todos", POST), Endpoint("/todos", DELETE), Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } } @Nested inner class GetMappingAnnotationTests { @Nested @WebMvcTest(GetMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using GetMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using GetMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class DeleteMappingAnnotationTests { @Nested @WebMvcTest(DeleteMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using DeleteMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using DeleteMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PatchMappingAnnotationTests { @Nested @WebMvcTest(PatchMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PatchMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PatchMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", PATCH), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PostMappingAnnotationTests { @Nested @WebMvcTest(PostMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PostMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PostMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", POST), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class PutMappingAnnotationTests { @Nested @WebMvcTest(PutMappingOneMediaTypeIsExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class OneMediaTypeIsExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at function level using PutMapping annotation is extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingMultipleMediaTypesAreExtractedCorrectlyController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class MultipleMediaTypesAreExtractedCorrectlyTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `multiple media types declared at function level using PutMapping annotation are extracted correctly`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DefinedOnFunctionUsingDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared at function level will fallback to default media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingWithoutProducesInfoAndStringAsResponseBodyValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoConsumesInfoAndStringAsReturnValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and kotlin String as request body will lead to plain text`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingNoProducesInfoAndNoReturnTypeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `no media type declared and no return type results in produces being empty`() { //given val specification: Set = setOf( Endpoint("/todos", PUT), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested inner class OverwriteTests { @Nested @WebMvcTest(RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingOneMediaTypeIsOverwrittenByDeclarationOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by a declaration at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class RequestMappingMultipleMediaTypesAreOverwrittenByDeclarationOnFunctionTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by a declaration at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos/{id}", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = POST, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PUT, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = PATCH, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint( path = "/todos/{id}", httpMethod = DELETE, produces = setOf(APPLICATION_XML_VALUE, APPLICATION_XHTML_XML_VALUE) ), Endpoint("/todos/{id}", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by GetMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by DeleteMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PatchMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PostMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingOneMediaTypeIsOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingOneMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PutMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(GetMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class GetMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by GetMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(DeleteMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class DeleteMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by DeleteMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = DELETE, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PatchMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PatchMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PatchMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PATCH, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PostMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PostMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PostMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = POST, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(PutMappingMultipleMediaTypesAreOverwrittenController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class PutMappingMultipleMediaTypeIsOverwrittenTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type declared at class level using RequestMapping is overwritten by PutMapping at function level`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = PUT, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(TEXT_PLAIN_VALUE, APPLICATION_PDF_VALUE) ), Endpoint("/todos", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } @Nested @WebMvcTest(ErrorEndpointController::class) inner class MediaTypeIsNotAddedToDefaultErrorEndpoint { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `error endpoint does not provide the same media type`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint( path = "/todos", httpMethod = HEAD, produces = setOf(APPLICATION_XML_VALUE) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/servletresponse/ProducesServletResponseTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.servletresponse import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.* import javax.servlet.http.HttpServletResponse @SpringBootApplication open class DummyApp @Controller open class ProducesServletResponseTestController { @GetMapping("/todos", produces = ["text/plain"]) @ResponseBody fun getTest(response: HttpServletResponse) { response.outputStream.println("Hello, world!") } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/produces/servletresponse/SpringConverterProducesServletResponseTest.kt ================================================ package de.codecentric.hikaku.converters.spring.produces.servletresponse import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext class SpringConverterProducesServletResponseTest { @Nested @WebMvcTest(ProducesServletResponseTestController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class NoProducesInfoAndNoReturnTypeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `media type and response servlet argument declared and no return type results in proper media type`() { //given val specification: Set = setOf( Endpoint("/todos", GET, produces = setOf("text/plain")), Endpoint("/todos", HEAD, produces = setOf("text/plain")), Endpoint("/todos", OPTIONS, produces = emptySet()) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/queryparameters/QueryParameterTestController.kt ================================================ package de.codecentric.hikaku.converters.spring.queryparameters import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam @SpringBootApplication open class DummyApp @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterNamedByVariableController { @GetMapping("/todos") fun getAllTodos(@RequestParam tag: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterNamedByValueAttributeController { @GetMapping("/todos") fun getAllTodos(@RequestParam(value = "tag") variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterNamedByNameAttributeController { @GetMapping("/todos") fun getAllTodos(@RequestParam(name = "tag") variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterHavingBothNameAndValueAttributeController { @GetMapping("/todos") fun getAllTodos(@RequestParam(value="valueAttribute", name = "nameAttribute") variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterOptionalController { @GetMapping("/todos") fun getAllTodos(@RequestParam(name = "tag", required = false) variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterOptionalBecauseOfDefaultValueController { @GetMapping("/todos") fun getAllTodos(@RequestParam(name = "tag", defaultValue = "mytag") variable: String) { } } @Controller @Suppress("UNUSED_PARAMETER") open class QueryParameterOnDefaultErrorEndpointController { @GetMapping("/todos") fun getAllTodos(@RequestParam(value = "tag", required = false) variable: String) { } } ================================================ FILE: spring/src/test/kotlin/de/codecentric/hikaku/converters/spring/queryparameters/SpringConverterQueryParameterTest.kt ================================================ package de.codecentric.hikaku.converters.spring.queryparameters import de.codecentric.hikaku.converters.spring.SpringConverter import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import de.codecentric.hikaku.endpoints.QueryParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.context.ConfigurableApplicationContext import org.springframework.http.MediaType import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import kotlin.test.assertFailsWith class SpringConverterQueryParameterTest { @Nested @WebMvcTest(QueryParameterNamedByVariableController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class QueryParameterNamedByVariableTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `query parameter name defined by variable name`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, queryParameters = setOf( QueryParameter("tag", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(QueryParameterNamedByValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class QueryParameterNamedByValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `query parameter name defined by attribute 'value'`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, queryParameters = setOf( QueryParameter("tag", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(QueryParameterNamedByNameAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class QueryParameterNamedByNameAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `query parameter name defined by attribute 'name'`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag", true) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, queryParameters = setOf( QueryParameter("tag", true) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(QueryParameterHavingBothNameAndValueAttributeController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class QueryParameterHavingBothNameAndValueAttributeTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `both 'value' and 'name' attribute defined for query parameter`() { assertFailsWith { SpringConverter(context).conversionResult } } } @Nested @WebMvcTest(QueryParameterOptionalController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class QueryParameterOptionalTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `query parameter set to optional`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag", false) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, queryParameters = setOf( QueryParameter("tag", false) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(QueryParameterOptionalBecauseOfDefaultValueController::class, excludeAutoConfiguration = [ErrorMvcAutoConfiguration::class]) inner class QueryParameterOptionalBecauseOfDefaultValueTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `query parameter optional, because of a default value`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag", false) ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, queryParameters = setOf( QueryParameter("tag", false) ) ) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } @Nested @WebMvcTest(QueryParameterOnDefaultErrorEndpointController::class) inner class QueryParameterOnDefaultErrorEndpointTest { @Autowired lateinit var context: ConfigurableApplicationContext @Test fun `query parameters are not added to default error endpoint`() { //given val specification: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, queryParameters = setOf( QueryParameter("tag") ) ), Endpoint("/todos", OPTIONS), Endpoint( path = "/todos", httpMethod = HEAD, queryParameters = setOf( QueryParameter("tag") ) ), Endpoint( path = "/error", httpMethod = GET, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(APPLICATION_JSON_VALUE) ), Endpoint("/error", OPTIONS), Endpoint( path = "/error", httpMethod = GET, produces = setOf(MediaType.TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = POST, produces = setOf(MediaType.TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = HEAD, produces = setOf(MediaType.TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PUT, produces = setOf(MediaType.TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = PATCH, produces = setOf(MediaType.TEXT_HTML_VALUE) ), Endpoint( path = "/error", httpMethod = DELETE, produces = setOf(MediaType.TEXT_HTML_VALUE) ), Endpoint("/error", OPTIONS) ) //when val implementation = SpringConverter(context) //then assertThat(implementation.conversionResult).containsExactlyInAnyOrderElementsOf(specification) } } } ================================================ FILE: spring/src/test/resources/.gitemptydir ================================================ ================================================ FILE: wadl/README.md ================================================ # hikaku - WADL ## Feature Support Please refer to the list of [all features](../docs/features.md). To check the feature support for this converter. ## Usage The WadlConverter can be used either with a `File` or a `Path` object. ================================================ FILE: wadl/build.gradle ================================================ group = 'de.codecentric.hikaku' archivesBaseName = 'hikaku-wadl' dependencies { api project(':core') } uploadArchives { repositories { mavenDeployer { pom.project { name = 'hikaku-wadl' description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains a converter for WADL files.' } } } } ================================================ FILE: wadl/src/main/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverter.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.SupportedFeatures import de.codecentric.hikaku.SupportedFeatures.Feature import de.codecentric.hikaku.converters.AbstractEndpointConverter import de.codecentric.hikaku.converters.EndpointConverterException import de.codecentric.hikaku.converters.wadl.extensions.getAttribute import de.codecentric.hikaku.endpoints.* import de.codecentric.hikaku.extensions.checkFileValidity import org.w3c.dom.Node import org.w3c.dom.NodeList import org.xml.sax.InputSource import java.io.File import java.io.StringReader import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Files import java.nio.file.Path import javax.xml.parsers.DocumentBuilderFactory import javax.xml.xpath.XPathConstants.NODESET import javax.xml.xpath.XPathFactory /** * Extracts and converts [Endpoint]s from a *.wadl* file. */ class WadlConverter private constructor(private val wadl: String) : AbstractEndpointConverter() { @JvmOverloads constructor(wadlFile: File, charset: Charset = UTF_8): this(wadlFile.toPath(), charset) @JvmOverloads constructor(wadlFile: Path, charset: Charset = UTF_8): this(readFileContent(wadlFile, charset)) override val supportedFeatures = SupportedFeatures( Feature.QueryParameters, Feature.HeaderParameters, Feature.PathParameters, Feature.MatrixParameters, Feature.Produces, Feature.Consumes ) private val xPath = XPathFactory .newInstance() .newXPath() override fun convert(): Set { try { return parseWadl() } catch (throwable: Throwable) { throw EndpointConverterException(throwable) } } private fun parseWadl(): Set { val doc = DocumentBuilderFactory .newInstance() .newDocumentBuilder() .parse(InputSource(StringReader(wadl))) val resources = xPath.evaluate("//resource", doc, NODESET) as NodeList val endpoints = mutableSetOf() for (index in 0 until resources.length) { endpoints.addAll(createEndpoints(resources.item(index))) } return endpoints } private fun createEndpoints(resourceElement: Node): Set { val path = resourceElement.getAttribute("path") val methods = xPath.evaluate("//resource[@path=\"$path\"]//method", resourceElement.childNodes, NODESET) as NodeList val endpoints: MutableSet = mutableSetOf() for (i in 0 until methods.length) { val method = methods.item(i) val httpMethod = HttpMethod.valueOf(method.getAttribute("name")) endpoints.add( Endpoint( path = path, httpMethod = httpMethod, queryParameters = extractQueryParameters(method), headerParameters = extractHeaderParameters(method), pathParameters = extractPathParameters(method), matrixParameters = extractMatrixParameters(method), produces = extractResponseMediaTypes(method), consumes = extractConsumesMediaTypes(method) ) ) } return endpoints } private fun extractResponseMediaTypes(method: Node) = extractMediaTypes(method, "response") private fun extractConsumesMediaTypes(method: Node) = extractMediaTypes(method, "request") private fun extractMediaTypes(method: Node, xmlBaseElement: String): Set { val representations = xPath.evaluate("//$xmlBaseElement/representation", method.childNodes, NODESET) as NodeList val mediaTypes: MutableSet = mutableSetOf() for (i in 0 until representations.length) { val parameter = representations.item(i) mediaTypes += parameter.getAttribute("mediaType") } return mediaTypes } private fun extractPathParameters(method: Node): Set { return extractParameter(method, "template") .entries .map { PathParameter(it.key) } .toSet() } private fun extractQueryParameters(method: Node): Set { return extractParameter(method, "query") .entries .map { QueryParameter(it.key, it.value) } .toSet() } private fun extractHeaderParameters(method: Node): Set { return extractParameter(method, "header") .entries .map { HeaderParameter(it.key, it.value) } .toSet() } private fun extractMatrixParameters(method: Node): Set { return extractParameter(method, "matrix") .entries .map { MatrixParameter(it.key, it.value) } .toSet() } private fun extractParameter(method: Node, style: String): Map { val parameters = xPath.evaluate("//param[@style=\"$style\"]", method.childNodes, NODESET) as NodeList val parameterMap: MutableMap = mutableMapOf() for (i in 0 until parameters.length) { val parameter = parameters.item(i) val parameterName = parameter.getAttribute("name") val isParameterRequired = "true" == parameter.getAttribute("required") parameterMap[parameterName] = isParameterRequired } return parameterMap } } private fun readFileContent(wadlFile: Path, charset: Charset): String { val fileContentBuilder = StringBuilder() try { wadlFile.checkFileValidity(".wadl") Files.readAllLines(wadlFile, charset) .map { line -> fileContentBuilder .append(line) .append("\n") } } catch (throwable: Throwable) { throw EndpointConverterException(throwable) } val fileContent = fileContentBuilder.toString() if (fileContent.isBlank()) { throw EndpointConverterException("Given WADL is blank.") } return fileContent } ================================================ FILE: wadl/src/main/kotlin/de/codecentric/hikaku/converters/wadl/extensions/NodeExtensions.kt ================================================ package de.codecentric.hikaku.converters.wadl.extensions import org.w3c.dom.Node internal fun Node.getAttribute(identifier: String): String { return this.attributes.getNamedItem(identifier).textContent } ================================================ FILE: wadl/src/main/resources/.gitemptydir ================================================ ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterConsumesTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterConsumesTest { @Test fun `check that media type information for the response are extracted correctly`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/consumes_three_media_types.wadl").toURI()) val implementation: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "application/xml", "text/plain" ) ) ) //when val specification = WadlConverter(file).conversionResult //then assertThat(specification).isEqualTo(implementation) } @Test fun `check that no media type information result in empty consumes list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/consumes_no_media_types.wadl").toURI()) val implementation: Set = setOf( Endpoint("/todos", GET) ) //when val specification = WadlConverter(file).conversionResult //then assertThat(specification).isEqualTo(implementation) } @Test fun `check that media types are not extracted from response info`() { //given val file = Paths.get(this::class.java.classLoader.getResource("consumes/consumes_media_types_not_taken_from_produces.wadl").toURI()) val implementation: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/xml", "text/plain" ) ) ) //when val specification = WadlConverter(file).conversionResult //then assertThat(specification).isEqualTo(implementation) } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterEndpointTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterEndpointTest { @Test fun `extract two different paths`() { //given val file = Paths.get(this::class.java.classLoader.getResource("endpoints/endpoints_two_different_paths.wadl").toURI()) val implementation: Set = setOf( Endpoint("/todos", GET), Endpoint("/tags", GET) ) //when val specification = WadlConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `extract two nested paths`() { //given val file = Paths.get(this::class.java.classLoader.getResource("endpoints/endpoints_two_nested_paths.wadl").toURI()) val implementation: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos/{id}", GET) ) //when val specification = WadlConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } @Test fun `extract all http methods`() { //given val file = Paths.get(this::class.java.classLoader.getResource("endpoints/endpoints.wadl").toURI()) val implementation: Set = setOf( Endpoint("/todos", GET), Endpoint("/todos", POST), Endpoint("/todos", PUT), Endpoint("/todos", PATCH), Endpoint("/todos", DELETE), Endpoint("/todos", HEAD), Endpoint("/todos", OPTIONS), Endpoint("/todos", TRACE), Endpoint("/tags", GET), Endpoint("/tags", POST), Endpoint("/tags", DELETE), Endpoint("/tags", HEAD), Endpoint("/tags", OPTIONS) ) //when val specification = WadlConverter(file) //then assertThat(specification.conversionResult).containsExactlyInAnyOrderElementsOf(implementation) } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterHeaderParameterTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.HeaderParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterHeaderParameterTest { @Test fun `check that header parameter are extracted correctly`() { //given val file = Paths.get(this::class.java.classLoader.getResource("header_parameters.wadl").toURI()) val headerParameters = setOf( HeaderParameter("x-b3-traceid", false), HeaderParameter("allow-cache", true) ) //when val specification = WadlConverter(file) //then val resultingHeaderParameters = specification.conversionResult.toList()[0].headerParameters assertThat(resultingHeaderParameters).containsExactlyInAnyOrderElementsOf(headerParameters) } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterInvalidInputTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.converters.EndpointConverterException import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.io.File import java.nio.file.Paths import kotlin.test.assertFailsWith class WadlConverterInvalidInputTest { @Nested inner class PathObjectTests { @Test fun `empty file returns an empty list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/empty_file.wadl").toURI()) //when assertFailsWith { WadlConverter(file).conversionResult } } @Test fun `file consisting solely of whitespaces returns an empty list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/whitespaces_only_file.wadl").toURI()) //when assertFailsWith { WadlConverter(file).conversionResult } } @Test fun `file containing syntax error`() { //given val file = Paths.get(this::class.java.classLoader.getResource("invalid_input/syntax_error.wadl").toURI()) val converter = WadlConverter(file) //when assertFailsWith { converter.conversionResult } } } @Nested inner class FileObjectTests { @Test fun `empty file returns an empty list`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/empty_file.wadl").toURI()) //when assertFailsWith { WadlConverter(file).conversionResult } } @Test fun `file consisting solely of whitespaces returns an empty list`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/whitespaces_only_file.wadl").toURI()) //when assertFailsWith { WadlConverter(file).conversionResult } } @Test fun `file containing syntax error`() { //given val file = File(this::class.java.classLoader.getResource("invalid_input/syntax_error.wadl").toURI()) val converter = WadlConverter(file) //when assertFailsWith { converter.conversionResult } } } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterMatrixParameterTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.MatrixParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterMatrixParameterTest { @Test fun `check that matrix parameter are extracted correctly`() { //given val file = Paths.get(this::class.java.classLoader.getResource("matrix_parameters.wadl").toURI()) val matrixParameters = setOf( MatrixParameter("done", false), MatrixParameter("tag", true) ) //when val specification = WadlConverter(file) //then val resultingMatrixParameters = specification.conversionResult.toList()[0].matrixParameters assertThat(resultingMatrixParameters).containsExactlyInAnyOrderElementsOf(matrixParameters) } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterPathParameterTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.PathParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterPathParameterTest { @Test fun `check that path parameter are extracted correctly`() { //given val file = Paths.get(this::class.java.classLoader.getResource("path_parameters.wadl").toURI()) val pathParameter = PathParameter("id") //when val specification = WadlConverter(file) //then val resultingPathParameters = specification.conversionResult.toList()[0].pathParameters assertThat(resultingPathParameters).containsExactly(pathParameter) } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterProducesTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.Endpoint import de.codecentric.hikaku.endpoints.HttpMethod.GET import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterProducesTest { @Test fun `check that media type information for the response are extracted correctly`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_three_media_types.wadl").toURI()) val implementation: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, produces = setOf( "application/json", "application/xml", "text/plain" ) ) ) //when val specification = WadlConverter(file).conversionResult //then assertThat(specification).isEqualTo(implementation) } @Test fun `check that no media type information result in empty consumes list`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_no_media_types.wadl").toURI()) val implementation: Set = setOf( Endpoint("/todos", GET) ) //when val specification = WadlConverter(file).conversionResult //then assertThat(specification).isEqualTo(implementation) } @Test fun `check that media types are not extracted from request info`() { //given val file = Paths.get(this::class.java.classLoader.getResource("produces/produces_media_types_not_taken_from_consumes.wadl").toURI()) val implementation: Set = setOf( Endpoint( path = "/todos", httpMethod = GET, consumes = setOf( "application/json", "application/xml", "text/plain" ) ) ) //when val specification = WadlConverter(file).conversionResult //then assertThat(specification).isEqualTo(implementation) } } ================================================ FILE: wadl/src/test/kotlin/de/codecentric/hikaku/converters/wadl/WadlConverterQueryParameterTest.kt ================================================ package de.codecentric.hikaku.converters.wadl import de.codecentric.hikaku.endpoints.QueryParameter import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.nio.file.Paths class WadlConverterQueryParameterTest { @Test fun `check that query parameter are extracted correctly`() { //given val file = Paths.get(this::class.java.classLoader.getResource("query_parameters.wadl").toURI()) val queryParameters = setOf( QueryParameter("tag", false), QueryParameter("limit", true) ) //when val specification = WadlConverter(file) //then val resultingQueryParameters = specification.conversionResult.toList()[0].queryParameters assertThat(resultingQueryParameters).containsExactlyInAnyOrderElementsOf(queryParameters) } } ================================================ FILE: wadl/src/test/resources/consumes/consumes_media_types_not_taken_from_produces.wadl ================================================ ================================================ FILE: wadl/src/test/resources/consumes/consumes_no_media_types.wadl ================================================ ================================================ FILE: wadl/src/test/resources/consumes/consumes_three_media_types.wadl ================================================ ================================================ FILE: wadl/src/test/resources/endpoints/endpoints.wadl ================================================ ================================================ FILE: wadl/src/test/resources/endpoints/endpoints_two_different_paths.wadl ================================================ ================================================ FILE: wadl/src/test/resources/endpoints/endpoints_two_nested_paths.wadl ================================================ ================================================ FILE: wadl/src/test/resources/header_parameters.wadl ================================================ ================================================ FILE: wadl/src/test/resources/invalid_input/empty_file.wadl ================================================ ================================================ FILE: wadl/src/test/resources/invalid_input/syntax_error.wadl ================================================ ources base="http://api.example.com/v1"> ================================================ FILE: wadl/src/test/resources/invalid_input/whitespaces_only_file.wadl ================================================ ================================================ FILE: wadl/src/test/resources/matrix_parameters.wadl ================================================ ================================================ FILE: wadl/src/test/resources/path_parameters.wadl ================================================ ================================================ FILE: wadl/src/test/resources/produces/produces_media_types_not_taken_from_consumes.wadl ================================================ ================================================ FILE: wadl/src/test/resources/produces/produces_no_media_types.wadl ================================================ ================================================ FILE: wadl/src/test/resources/produces/produces_three_media_types.wadl ================================================ ================================================ FILE: wadl/src/test/resources/query_parameters.wadl ================================================