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
[](https://travis-ci.org/codecentric/hikaku) [](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