From 82cc94d5f328570f864e2f94d28a1d8a12a990ea Mon Sep 17 00:00:00 2001 From: Ian Griffiths Date: Thu, 5 Jan 2023 13:35:28 +0000 Subject: [PATCH] Improve testing for converters (#316) This revealed that the ConvertTo for these methods didn't seem to work at all. (It would invariably fail when trying to validate its converted result, because it creates a string, but validation for these types fails unless the input is of the corresponding JToken type.) Also, there were inconsistencies in how validation failures were being reported, which this fixes. Note that this now uses the .NET 7 SDK in build pipeline (but only for access to new language features - there's no change in target runtimes). --- .../Menes/Converters/BooleanConverter.cs | 2 +- .../Menes/Converters/ByteArrayConverter.cs | 2 +- .../Menes/Converters/DateConverter.cs | 23 +- .../Menes/Converters/DoubleConverter.cs | 2 +- .../Menes/Converters/FloatConverter.cs | 2 +- .../Menes/Converters/IOpenApiConverter.cs | 47 +- .../Menes/Converters/Integer32Converter.cs | 14 +- .../Menes/Converters/Integer64Converter.cs | 15 +- .../Menes/Converters/PasswordConverter.cs | 2 +- .../Menes/Converters/StringConverter.cs | 2 +- .../Menes/Converters/UriConverter.cs | 2 +- ...sControlPolicyEvaluationFailedException.cs | 1 - .../Exceptions/OpenApiBadRequestException.cs | 9 + .../OpenApiInvalidFormatException.cs | 82 ++ .../Validation/OpenApiSchemaValidator.cs | 20 +- .../Internal/HttpRequestParameterBuilder.cs | 247 ++-- .../packages.lock.json | 12 +- .../packages.lock.json | 12 +- .../Menes.PetStore.Specs/packages.lock.json | 78 +- .../Bindings/MenesContainerBindings.cs | 14 +- .../Menes.Specs/Fakes/ObjectWithIdAndName.cs | 12 + .../Fakes/RegisteredContentType1.cs | 15 + .../Fakes/RegisteredContentType2.cs | 15 + .../Fakes/RegisteredDiscriminatedType1.cs | 12 + .../Fakes/RegisteredDiscriminatedType2.cs | 12 + .../ArrayInputParsing.feature | 213 ++++ .../BooleanInputParsing.feature | 181 +++ .../BooleanOutputParsing.feature | 17 + .../ByteArrayInputParsing.feature | 96 ++ .../ByteArrayOutputParsing.feature | 17 + .../DateInputParsing.feature | 101 ++ .../DateOutputParsing.feature | 16 + .../DateTimeInputParsing.feature | 106 ++ .../DateTimeOutputParsing.feature | 16 + .../DoubleInputParsing.feature | 177 +++ .../DoubleOutputParsing.feature | 21 + .../FloatInputParsing.feature | 175 +++ .../FloatOutputParsing.feature | 20 + .../Integer32InputParsing.feature | 162 +++ .../Integer32OutputParsing.feature | 20 + .../Integer64InputParsing.feature | 180 +++ .../Integer64OutputParsing.feature | 22 + .../ObjectInputParsing.feature | 215 ++++ .../ObjectOutputParsing.feature | 12 + .../OpenApiDefaultParameterParsing.feature | 97 ++ .../PasswordInputParsing.feature | 96 ++ .../PasswordOutputParsing.feature | 16 + .../StringInputParsing.feature | 141 +++ .../UriInputParsing.feature | 109 ++ .../UriOutputParsing.feature | 20 + .../UuidInputParsing.feature | 99 ++ .../UuidOutputParsing.feature | 16 + .../OpenApiDefaultParameterParsing.feature | 94 -- .../Steps/LinkCollectionExtensionsSteps.cs | 4 +- .../OpenApiDefaultParameterParsingSteps.cs | 169 --- .../Steps/OpenApiParameterParsingSteps.cs | 1121 +++++++++++++++++ .../Steps/OpenApiValidationSteps.cs | 2 +- .../Steps/OpenApiWebLinkResolverSteps.cs | 7 +- .../OperationInvokerTestContext.cs | 5 +- Solutions/Menes.Specs/packages.lock.json | 78 +- azure-pipelines.yml | 3 +- 61 files changed, 4007 insertions(+), 491 deletions(-) create mode 100644 Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs create mode 100644 Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs create mode 100644 Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs create mode 100644 Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs create mode 100644 Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs create mode 100644 Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature create mode 100644 Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature delete mode 100644 Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature delete mode 100644 Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs create mode 100644 Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs diff --git a/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs index 7842e9eab..78e571d30 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/BooleanConverter.cs @@ -50,7 +50,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs index ae7f2280f..f3e65d0cf 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/ByteArrayConverter.cs @@ -46,7 +46,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) this.validator.ValidateAndThrow(result, schema); - return result; + return $"\"{result}\""; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs index ba378ad71..42f5e4b84 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/DateConverter.cs @@ -5,9 +5,11 @@ namespace Menes.Converters { using System; + using Menes.Validation; + using Microsoft.OpenApi.Models; - using Newtonsoft.Json; + using Newtonsoft.Json.Linq; /// @@ -16,17 +18,14 @@ namespace Menes.Converters public class DateConverter : IOpenApiConverter { private readonly OpenApiSchemaValidator validator; - private readonly IOpenApiConfiguration configuration; /// /// Initializes a new instance of the class. /// /// The . - /// The OpenAPI host configuration. - public DateConverter(OpenApiSchemaValidator validator, IOpenApiConfiguration configuration) + public DateConverter(OpenApiSchemaValidator validator) { this.validator = validator; - this.configuration = configuration; } /// @@ -39,9 +38,9 @@ public bool CanConvert(OpenApiSchema schema, bool ignoreFormat = false) public object ConvertFrom(string content, OpenApiSchema schema) { JToken token = content; - var result = (DateTimeOffset)token; + var result = new DateTimeOffset(DateTime.SpecifyKind((DateTime)token, DateTimeKind.Utc)); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(token, schema); return result; } @@ -49,9 +48,15 @@ public object ConvertFrom(string content, OpenApiSchema schema) /// public string ConvertTo(object instance, OpenApiSchema schema) { - string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); + string result = instance switch + { + DateTimeOffset dt => $"\"{dt:yyyy-MM-dd}\"", + DateTime dt => $"\"{dt:yyyy-MM-dd}\"", + DateOnly dt => $"\"{dt:yyyy-MM-dd}\"", + _ => throw new ArgumentException($"Unsupported source type {instance.GetType().FullName}"), + }; - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs index f7a3f5ff1..49f59e6bd 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/DoubleConverter.cs @@ -49,7 +49,7 @@ public object ConvertFrom(string content, OpenApiSchema schema) public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs index f124c88d3..ce3e044fb 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/FloatConverter.cs @@ -51,7 +51,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs index c9033b5da..21b35a80f 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/IOpenApiConverter.cs @@ -9,6 +9,42 @@ namespace Menes.Converters /// /// Implemented by types that can convert a value to/from an OpenAPI schema value. /// + /// + /// + /// This interface has a slight internal inconsistency: string value handling is different for + /// inputs () and outputs + /// (). String-typed inputs normally come from + /// the path, query, or sometimes a header or cookie, and in these cases, we don't wrap the + /// strings with double quotes. This is in contrast to JSON where you can always tell the + /// difference between a string value and some other value, e.g.: + /// + /// + /// { + /// "booleanValue": true, + /// "stringValue": "true" + /// + /// + /// If inputs were always in JSON form, you'd see a similar difference. For example, if an + /// input appears in a query string, http://example.com/?x=true would unambiguously + /// mean that the parameter x has the JSON boolean value true, whereas if we wanted a + /// string value, we'd use http://example.com/?x=%22true%22. But in practice, it's + /// unusual for web sites to use this convention. In practice, all query string parameters + /// have string values, and it's up to the web server whether it chooses to interpret them + /// as having a particular format. + /// + /// + /// For this reason, this interface expects incoming string values to be unquoted, because + /// in most cases they are. The one exception is if the entire request body is a single + /// string. If the request Content-Type is application/json then it must be + /// enclosing in double quotes. But for anything other than the body, those quotes will not + /// be present in the raw inputs. But outputs are always in JSON form, + /// returns quoted + /// strings. We could have insisted on consistency here, but that would have required almost + /// all calls to to create new strings + /// wrapping the existing input values in quotes. This would add noise to the code, and require + /// additional string allocations, so instead, we just live with asymmetry in this interface. + /// + /// public interface IOpenApiConverter { /// @@ -22,7 +58,10 @@ public interface IOpenApiConverter /// /// Convert from the specified content to an object of the required type. /// - /// The content to convert. + /// + /// The content to convert. If the input data is of type string, this will not be + /// enclosed in double quotes. + /// /// The schema of the content to convert. /// An instance of the converted object. object ConvertFrom(string content, OpenApiSchema schema); @@ -32,7 +71,11 @@ public interface IOpenApiConverter /// /// The instance to convert. /// The schema of the content to convert. - /// A string representation of the converted object for the output document. + /// + /// A JSON representation of the converted object for the output document. Unlike with + /// , if the data is a JSON string, + /// it will be enclosed in double quotes. + /// string ConvertTo(object instance, OpenApiSchema schema); } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs b/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs index 9db5a3b07..b685ba116 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/Integer32Converter.cs @@ -4,6 +4,8 @@ namespace Menes.Converters { + using System; + using Menes.Validation; using Microsoft.OpenApi.Models; using Newtonsoft.Json; @@ -39,7 +41,15 @@ public object ConvertFrom(string content, OpenApiSchema schema) { JToken token = content; - int result = (int)token; + int result; + try + { + result = (int)token; + } + catch (OverflowException) + { + throw new FormatException("Number was too large to parse as an Int32"); + } this.validator.ValidateAndThrow(result, schema); @@ -51,7 +61,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs b/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs index 13c4ac730..89fde734e 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/Integer64Converter.cs @@ -4,6 +4,8 @@ namespace Menes.Converters { + using System; + using Menes.Validation; using Microsoft.OpenApi.Models; using Newtonsoft.Json; @@ -39,7 +41,16 @@ public object ConvertFrom(string content, OpenApiSchema schema) { JToken token = content; - long result = (long)token; + long result; + try + { + result = (long)token; + } + catch (OverflowException) + { + throw new FormatException("Number was too large to parse as an Int64"); + } + this.validator.ValidateAndThrow(result, schema); return result; @@ -50,7 +61,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs index 65371763a..a858a0472 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/PasswordConverter.cs @@ -44,7 +44,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) this.validator.ValidateAndThrow(result, schema); - return result; + return $"\"{result}\""; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs index d9f33feb5..e37c06211 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/StringConverter.cs @@ -53,7 +53,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) this.validator.ValidateAndThrow(result, schema); - return result; + return '"' + result + '"'; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs b/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs index e0d24929a..98b237cf1 100644 --- a/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs +++ b/Solutions/Menes.Abstractions/Menes/Converters/UriConverter.cs @@ -51,7 +51,7 @@ public string ConvertTo(object instance, OpenApiSchema schema) { string result = JsonConvert.SerializeObject(instance, this.configuration.Formatting, this.configuration.SerializerSettings); - this.validator.ValidateAndThrow(result, schema); + this.validator.ValidateAndThrow(JToken.Parse(result), schema); return result; } diff --git a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs index aaebc09b2..2a3b43cef 100644 --- a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs +++ b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiAccessControlPolicyEvaluationFailedException.cs @@ -6,7 +6,6 @@ namespace Menes.Exceptions { using System; using System.Linq; - using System.Runtime.Serialization; /// /// An exception that is thrown by an if it fails diff --git a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs index 06c0d3014..f51f6aa8e 100644 --- a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs +++ b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiBadRequestException.cs @@ -94,6 +94,15 @@ public OpenApiBadRequestException( } } + /// + /// Creates a from an . + /// + /// The . + public OpenApiBadRequestException(OpenApiInvalidFormatException formatException) + : this(formatException.Message, formatException.Explanation!, formatException.DetailsType, formatException.DetailsInstance) + { + } + /// /// Gets the detailed explanation of the problem, if present. /// diff --git a/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs new file mode 100644 index 000000000..8cd3c4df0 --- /dev/null +++ b/Solutions/Menes.Abstractions/Menes/Exceptions/OpenApiInvalidFormatException.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Exceptions +{ + using System; + + /// + /// An exception that indicates that data has been found not to be in the format required + /// by an OpenAPI specification. + /// + /// + /// + /// We share validation code across multiple contexts (validation of inputs, default values + /// in an Open API spec, and outputs) and the required externally visible behaviour depends on + /// the context in which the problem is detected. The common layer needs to be able to throw + /// an exception indicating only that a problem has been detected, and this then needs to be + /// turned into a more suitable exception by the higher layers of code. + /// + /// + public class OpenApiInvalidFormatException : Exception + { + /// + /// Creates an . + /// + /// + /// The value for this exception's property. + /// + public OpenApiInvalidFormatException(string message = "Bad request") + : base(message) + { + } + + /// + /// Creates an . + /// + /// + /// A short description of the error. This is used as the + /// property, and will also be returned in the Problem Details in the response body. + /// + /// + /// A longer description of the problem. This is returned in the Problem Details in the + /// response body. + /// + /// + /// An optional URI identifying the problem type. If specified, this is returned in the + /// Problem Details in the response body. + /// response body. + /// + /// + /// An optional URI identifying the problem instance. If specified, this is returned in the + /// Problem Details in the response body. + /// + public OpenApiInvalidFormatException( + string title, + string explanation, + string? detailsType = null, + string? detailsInstance = null) + : base(title) + { + this.Explanation = explanation; + this.DetailsType = detailsType; + this.DetailsInstance = detailsInstance; + } + + /// + /// Gets the detailed explanation of the problem, if present. + /// + public string? Explanation { get; } + + /// + /// Gets the URI identifying the problem type, if present. + /// + public string? DetailsType { get; } + + /// + /// Gets the URI identifying the problem instance, if present. + /// + public string? DetailsInstance { get; } + } +} \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs b/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs index 0e7d09efe..8b983e84c 100644 --- a/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs +++ b/Solutions/Menes.Abstractions/Menes/Validation/OpenApiSchemaValidator.cs @@ -19,6 +19,8 @@ namespace Menes.Validation using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Writers; + + using Newtonsoft.Json; using Newtonsoft.Json.Linq; /// Class to validate a JSON schema against a given . @@ -41,7 +43,7 @@ public OpenApiSchemaValidator(ILogger logger) this.logger = logger; } - /// Validates the given data, throwing an augmented with detailing the errors. + /// Validates the given data, throwing an augmented with detailing the errors. /// The data. /// The schema. public void ValidateAndThrow(string data, OpenApiSchema schema) @@ -55,7 +57,7 @@ public void ValidateAndThrow(string data, OpenApiSchema schema) if (errors.Count > 0) { - Exception exception = new OpenApiBadRequestException("Schema Validation Error", "The content does not conform to the required schema.") + Exception exception = new OpenApiInvalidFormatException("Schema Validation Error", "The content does not conform to the required schema.") .AddProblemDetailsExtension("validationErrors", errors); this.logger.LogError( @@ -66,7 +68,7 @@ public void ValidateAndThrow(string data, OpenApiSchema schema) } } - /// Validates the given data, throwing an augmented with detailing the errors. + /// Validates the given data, throwing an augmented with detailing the errors. /// The data. /// The schema. public void ValidateAndThrow(JToken? data, OpenApiSchema schema) @@ -82,7 +84,7 @@ public void ValidateAndThrow(JToken? data, OpenApiSchema schema) if (errors.Count > 0) { - Exception exception = new OpenApiBadRequestException("Schema Validation Error", "The content does not conform to the required schema.") + Exception exception = new OpenApiInvalidFormatException("Schema Validation Error", "The content does not conform to the required schema.") .AddProblemDetailsExtension("validationErrors", errors); throw exception; @@ -96,7 +98,15 @@ public void ValidateAndThrow(JToken? data, OpenApiSchema schema) private static ICollection Validate(string data, OpenApiSchema schema) { bool dataShouldBeJson = schema.Type == "object" || schema.Type == "array" || schema.Type == null; - JToken jsonObject = dataShouldBeJson ? JToken.Parse(data) : data; + JToken jsonObject; + try + { + jsonObject = dataShouldBeJson ? JToken.Parse(data) : data; + } + catch (JsonReaderException x) + { + throw new OpenApiInvalidFormatException($"Expected object or array. Failed to parse: {x.Message}"); + } return Validate(jsonObject, schema); } diff --git a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs index f6788db0a..147ea8866 100644 --- a/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs +++ b/Solutions/Menes.Hosting.AspNetCore/Menes/Internal/HttpRequestParameterBuilder.cs @@ -8,7 +8,6 @@ namespace Menes.Internal using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; - using System.Linq; using System.Threading.Tasks; using Menes.Converters; using Menes.Exceptions; @@ -58,107 +57,118 @@ public async Task> BuildParametersAsync(HttpRequest this.logger.LogDebug("Building parameters for parameter [{parameter}]", parameter.Name); } - switch (parameter.In) + try { - case ParameterLocation.Cookie: - if (this.TryGetParameterFromCookie(request.Cookies, parameter, out object? cookieResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + switch (parameter.In) + { + case ParameterLocation.Cookie: + if (this.TryGetParameterFromCookie(request.Cookies, parameter, out object? cookieResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from cookie for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, cookieResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from cookie for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required cookie parameters from the cookie for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing cookie", + $"Failed to get the required cookie for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, cookieResult); - } - else if (parameter.Required) - { - this.logger.LogError( - "Failed to get the required cookie parameters from the cookie for the parameter [{parameter}]", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing cookie", - $"Failed to get the required cookie for the parameter {parameter.Name}"); - } - - break; - case ParameterLocation.Header: - if (this.TryGetParameterFromHeader(request.Headers, parameter, out object? headerResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + break; + case ParameterLocation.Header: + if (this.TryGetParameterFromHeader(request.Headers, parameter, out object? headerResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from headers for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, headerResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from headers for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required header parameters from the header for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing header", + $"Failed to get the required header for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, headerResult); - } - else if (parameter.Required) - { - this.logger.LogError( - "Failed to get the required header parameters from the header for the parameter [{parameter}]", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing header", - $"Failed to get the required header for the parameter {parameter.Name}"); - } - - break; - case ParameterLocation.Path: - if (this.TryGetParameterFromPath(templateParameterValues, parameter, out object? pathResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + break; + case ParameterLocation.Path: + if (this.TryGetParameterFromPath(templateParameterValues, parameter, out object? pathResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from path for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, pathResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from path for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required path parameters from the path for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing path parameter", + $"Failed to get the required path element for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, pathResult); - } - else if (parameter.Required) - { - this.logger.LogError( - "Failed to get the required path parameters from the path for the parameter [{parameter}]", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing path parameter", - $"Failed to get the required path element for the parameter {parameter.Name}"); - } - - break; - case ParameterLocation.Query: - if (this.TryGetParameterFromQuery(request.Query, parameter, out object? queryResult)) - { - if (this.logger.IsEnabled(LogLevel.Debug)) + break; + case ParameterLocation.Query: + if (this.TryGetParameterFromQuery(request.Query, parameter, out object? queryResult)) + { + if (this.logger.IsEnabled(LogLevel.Debug)) + { + this.logger.LogDebug( + "Added parameter from query for parameter [{parameter}]", + parameter.Name); + } + + parameters.Add(parameter.Name, queryResult); + } + else if (parameter.Required) { - this.logger.LogDebug( - "Added parameter from query for parameter [{parameter}]", + this.logger.LogError( + "Failed to get the required query parameters from the query for the parameter [{parameter}]", parameter.Name); + throw new OpenApiBadRequestException( + "Missing query parameter", + $"Failed to get the required query string entry for the parameter {parameter.Name}"); } - parameters.Add(parameter.Name, queryResult); - } - else if (parameter.Required) - { + break; + default: this.logger.LogError( - "Failed to get the required query parameters from the query for the parameter [{parameter}]", + "Failed to get the required parameters for the parameter [{parameter}], case not found", parameter.Name); throw new OpenApiBadRequestException( - "Missing query parameter", - $"Failed to get the required query string entry for the parameter {parameter.Name}"); - } - - break; - default: - this.logger.LogError( - "Failed to get the required parameters for the parameter [{parameter}], case not found", - parameter.Name); - throw new OpenApiBadRequestException( - "Missing parameter", - $"Failed to get the required parameter {parameter.Name}"); + "Missing parameter", + $"Failed to get the required parameter {parameter.Name}"); + } + } + catch (OpenApiInvalidFormatException x) + { + throw new OpenApiBadRequestException(x); + } + catch (FormatException) + { + throw new OpenApiBadRequestException($"Parameter {parameter} did not have the required format"); } } @@ -189,24 +199,35 @@ public async Task> BuildParametersAsync(HttpRequest private object? TryGetDefaultValueFromSchema(OpenApiSchema schema, IOpenApiAny? defaultValue) { - return defaultValue switch - { - //// Convert to DateTimeOffset rather than use the DateTime supplied by default to be consistent with our converters. - OpenApiDate d when schema.Format == "date" => new DateTimeOffset(DateTime.SpecifyKind(d.Value, DateTimeKind.Utc)), - //// Convert to string rather than use the DateTimeOffset supplied by default to be consistent with our current (lack of) support for strings with format "date-time". - OpenApiDateTime dt when schema.Format == "date-time" => dt.Value.ToString("yyyy-MM-ddTHH:mm:ssK"), - OpenApiPassword p when schema.Format == "password" => p.Value, - OpenApiByte by when schema.Format == "byte" => by.Value, - OpenApiString s when schema.Type == "string" => this.ConvertValue(schema, s.Value), - OpenApiBoolean b when schema.Type == "boolean" => b.Value, - OpenApiLong l when schema.Format == "int64" => l.Value, - OpenApiInteger i when schema.Type == "integer" => i.Value, - OpenApiFloat f when schema.Format == "float" => f.Value, - OpenApiDouble db when schema.Type == "number" => db.Value, - OpenApiArray a when schema.Type == "array" => HandleArray(schema.Items, a), - OpenApiObject o when schema.Type == "object" => HandleObject(schema.Properties, o), - _ => throw new OpenApiSpecificationException("Default value for parameter not valid."), - }; + try + { + return defaultValue switch + { + //// Convert to DateTimeOffset rather than use the DateTime supplied by default to be consistent with our converters. + OpenApiDate d when schema.Format == "date" => new DateTimeOffset(DateTime.SpecifyKind(d.Value, DateTimeKind.Utc)), + //// Convert to string rather than use the DateTimeOffset supplied by default to be consistent with our current (lack of) support for strings with format "date-time". + OpenApiDateTime dt when schema.Format == "date-time" => dt.Value.ToString("yyyy-MM-ddTHH:mm:ssK"), + OpenApiPassword p when schema.Format == "password" => p.Value, + OpenApiByte by when schema.Format == "byte" => by.Value, + OpenApiString s when schema.Type == "string" => this.ConvertValue(schema, s.Value), + OpenApiBoolean b when schema.Type == "boolean" => b.Value, + OpenApiLong l when schema.Format == "int64" => l.Value, + OpenApiInteger i when schema.Type == "integer" => i.Value, + OpenApiFloat f when schema.Format == "float" => f.Value, + OpenApiDouble db when schema.Type == "number" => db.Value, + OpenApiArray a when schema.Type == "array" => HandleArray(schema.Items, a), + OpenApiObject o when schema.Type == "object" => HandleObject(schema.Properties, o), + _ => throw new OpenApiSpecificationException("Default value for parameter not valid."), + }; + } + catch (OpenApiInvalidFormatException x) + { + throw new OpenApiSpecificationException($"Default value for parameter not valid ({x.Message})."); + } + catch (FormatException) + { + throw new OpenApiSpecificationException("Default value for parameter not valid."); + } object HandleArray(OpenApiSchema schema, OpenApiArray array) { @@ -413,7 +434,31 @@ private async Task ConvertBodyAsync(OpenApiSchema schema, Stream body) value = await reader.ReadToEndAsync().ConfigureAwait(false); } - return this.ConvertValue(schema, value); + try + { + // The funky thing about IOpenApiConverter is that although in most cases + // the string we pass for conversion is in its JSON format, strings are the + // exception: those appear unquoted. That's because converters are also + // passed values from headers, query strings, paths, and cookies, where + // strings are not quoted. + // Since we know we're receiving actual JSON, it's our job to detect when + // it's a string, and if it is, strip off the quotes and process any escaped + // characters inside. + if (value.Length >= 2 && value[0] == '"' && value[^1] == '"') + { + value = JsonConvert.DeserializeObject(value)!; + } + + return this.ConvertValue(schema, value); + } + catch (OpenApiInvalidFormatException x) + { + throw new OpenApiBadRequestException(x); + } + catch (FormatException) + { + throw new OpenApiBadRequestException("Request body did not have the required format"); + } } private bool TryGetParameterFromHeader( diff --git a/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json b/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json index ddff2b293..02e4ee358 100644 --- a/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json +++ b/Solutions/Menes.PetStore.Hosting.AspNetCore.DirectPipeline/packages.lock.json @@ -192,15 +192,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -1113,7 +1113,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json b/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json index 6915b667c..5ca10e7cc 100644 --- a/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json +++ b/Solutions/Menes.PetStore.Hosting.AzureFunctions/packages.lock.json @@ -663,15 +663,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -1744,7 +1744,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/Solutions/Menes.PetStore.Specs/packages.lock.json b/Solutions/Menes.PetStore.Specs/packages.lock.json index bf6f7a184..392fceace 100644 --- a/Solutions/Menes.PetStore.Specs/packages.lock.json +++ b/Solutions/Menes.PetStore.Specs/packages.lock.json @@ -4,15 +4,15 @@ "net6.0": { "Corvus.Testing.SpecFlow.NUnit": { "type": "Direct", - "requested": "[1.6.0, )", - "resolved": "1.6.0", - "contentHash": "V8v7+oRFK99skWq/TmzgiiS6KYQtu/gJXTXep8HKxt3u1rbU7BZFQfPCHiyH7KSAIn+4HEnxqlvCbXUSyRGCAA==", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "mutrleL9V2ftOzcrfBGSe8imboy+p4hxWO+ejNE6TWvBPBdjFlteST/vsHoQwMOVT1VUj4AfXtgSqk5SN+NH4A==", "dependencies": { - "Corvus.Testing.SpecFlow": "1.6.0", - "Microsoft.NET.Test.Sdk": "16.11.0", - "Moq": "4.18.2", + "Corvus.Testing.SpecFlow": "2.0.0", + "Microsoft.NET.Test.Sdk": "17.4.0", + "Moq": "4.18.3", "SpecFlow.NUnit.Runners": "3.9.74", - "coverlet.msbuild": "3.1.2" + "coverlet.msbuild": "3.2.0" } }, "Endjin.RecommendedPractices.GitHub": { @@ -96,12 +96,12 @@ }, "Corvus.Testing.SpecFlow": { "type": "Transitive", - "resolved": "1.6.0", - "contentHash": "0Qg8suJQ6TbvQK1ND51LK6kP0x1lZfqQVpbLf0CqKcIVLrJLmsb2Az+j635D6RJNTZ6jYcWxxtFEh314bERaMQ==", + "resolved": "2.0.0", + "contentHash": "dNCAIMrssh6jG7HO8FlTRzdNIHcE/X8B6Zlsdfwi9LbI0EX2IZGiFZosp+XhFQDYZ5ttAzJjO2ZWUqhnpglWWQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.31", - "Microsoft.Extensions.DependencyInjection": "3.1.31", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.31", + "Microsoft.Extensions.Configuration.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection": "6.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "NUnit": "3.13.3", "SpecFlow": "3.9.74", "System.Management": "4.7.0" @@ -109,8 +109,8 @@ }, "coverlet.msbuild": { "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "QhM0fnDtmIMImY7oxyQ/kh1VYtRxPyRVeLwRUGuUvI6Xp83pSYG9gerK8WgJj4TzUl7ISziADUGtIWKhtlbkbQ==" + "resolved": "3.2.0", + "contentHash": "lu/eJJpqJb4qy3BGPtDD/LI5RSOwXYYyRErTyaG0OTP69llzVK3FEe74hBQx0JtLUTLEVBfERV4uGYcE1Br2sg==" }, "Endjin.RecommendedPractices": { "type": "Transitive", @@ -630,8 +630,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "wf6lpAeCqP0KFfbDVtfL50lr7jY1gq0+0oSphyksfLOEygMDXqnaxHK5LPFtMEhYSEtgXdNyXNnEddOqQQUdlQ==" + "resolved": "17.4.0", + "contentHash": "2oZbSVTC2nAvQ2DnbXLlXS+c25ZyZdWeNd+znWwAxwGaPh9dwQ5NBsYyqQB7sKmJKIUdkKGmN3rzFzjVC81Dtg==" }, "Microsoft.CSharp": { "type": "Transitive", @@ -738,8 +738,8 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "6.0.1", + "contentHash": "vWXPg3HJQIpZkENn1KWq8SfbqVujVD7S7vIAyFXXqK5xkf1Vho+vG0bLBCHxU36lD1cLLtmGpfYf0B3MYFi9tQ==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0" @@ -938,11 +938,11 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "f4mbG1SUSkNWF5p7B3Y8ZxMsvKhxCmpZhdl+w6tMtLSUGE7Izi1syU6TkmKOvB2BV66pdbENConFAISOix4ohQ==", + "resolved": "17.4.0", + "contentHash": "VtNZQ83ntG2aEUjy1gq6B4HNdn96se6FmdY/03At8WiqDReGrApm6OB2fNiSHz9D6IIEtWtNZ2FSH0RJDVXl/w==", "dependencies": { - "Microsoft.CodeCoverage": "16.11.0", - "Microsoft.TestPlatform.TestHost": "16.11.0" + "Microsoft.CodeCoverage": "17.4.0", + "Microsoft.TestPlatform.TestHost": "17.4.0" } }, "Microsoft.NETCore.Platforms": { @@ -957,15 +957,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -985,20 +985,20 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "EiknJx9N9Z30gs7R+HHhki7fA8EiiM3pwD1vkw3bFsBC8kdVq/O7mHf1hrg5aJp+ASO6BoOzQueD2ysfTOy/Bg==", + "resolved": "17.4.0", + "contentHash": "oWe7A0wrZhxagTOcaxJ9r0NXTbgkiBQQuCpCXxnP06NsGV/qOoaY2oaangAJbOUrwEx0eka1do400NwNCjfytw==", "dependencies": { - "NuGet.Frameworks": "5.0.0", + "NuGet.Frameworks": "5.11.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "/Q+R0EcCJE8JaYCk+bGReicw/xrB0HhecrYrUcLbn95BnAlaTJrZhoLkUhvtKTAVtqX/AIKWXYtutiU/Q6QUgg==", + "resolved": "17.4.0", + "contentHash": "sUx48fu9wgQF1JxzXeSVtzb7KoKpJrdtIzsFamxET3ZYOKXj+Ej13HWZ0U2nuMVZtZVHBmE+KS3Vv5cIdTlycQ==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "16.11.0", - "Newtonsoft.Json": "9.0.1" + "Microsoft.TestPlatform.ObjectModel": "17.4.0", + "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { @@ -1022,8 +1022,8 @@ }, "Moq": { "type": "Transitive", - "resolved": "4.18.2", - "contentHash": "SjxKYS5nX6prcaT8ZjbkONh3vnh0Rxru09+gQ1a07v4TM530Oe/jq3Q4dOZPfo1wq0LYmTgLOZKrqRfEx4auPw==", + "resolved": "4.18.3", + "contentHash": "nmV2lludVOFmVi+Vtq9twX1/SDiEVyYDURzxW39gUBqjyoXmdyNwJSeOfSCJoJTXDXBVfFNfEljB5UWGj/cKnQ==", "dependencies": { "Castle.Core": "5.1.0" } @@ -1065,8 +1065,8 @@ }, "NuGet.Frameworks": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" + "resolved": "5.11.0", + "contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q==" }, "NUnit": { "type": "Transitive", @@ -2264,7 +2264,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs b/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs index a6723245f..d9617bb1b 100644 --- a/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs +++ b/Solutions/Menes.Specs/Bindings/MenesContainerBindings.cs @@ -4,6 +4,7 @@ namespace Marain.Claims.SpecFlow.Bindings { + using Corvus.ContentHandling; using Corvus.Monitoring.Instrumentation; using Corvus.Testing.SpecFlow; using Menes; @@ -30,7 +31,18 @@ public static void InitializeContainer(ScenarioContext scenarioContext) serviceCollection => { serviceCollection.AddLogging(); - serviceCollection.AddOpenApiActionResultHosting(null); + serviceCollection.AddOpenApiAspNetPipelineHosting( + null, + config => + { + config.DiscriminatedTypes.Add("registeredDiscriminatedType1", typeof(RegisteredDiscriminatedType1)); + config.DiscriminatedTypes.Add("registeredDiscriminatedType2", typeof(RegisteredDiscriminatedType2)); + }); + serviceCollection.AddContent(cf => + { + cf.RegisterTransientContent(); + cf.RegisterTransientContent(); + }); var instrumentationProvider = new FakeInstrumentationProvider(); serviceCollection.AddSingleton(instrumentationProvider); diff --git a/Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs b/Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs new file mode 100644 index 000000000..0dbb00212 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/ObjectWithIdAndName.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +/// +/// Type used to test serialization of responses from objects with simple properties. +/// +/// A unique id. +/// A name. +public record ObjectWithIdAndName(int Id, string Name); \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs b/Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs new file mode 100644 index 000000000..43df276d3 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredContentType1.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +#pragma warning disable RCS1102 // Make class static. + +/// +/// A class used to test object deserialization based on Corvus.ContentHandling. +/// +public class RegisteredContentType1 +{ + public const string RegisteredContentType = "registeredContentType1"; +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs b/Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs new file mode 100644 index 000000000..d241c8542 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredContentType2.cs @@ -0,0 +1,15 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +#pragma warning disable RCS1102 // Make class static. + +/// +/// A class used to test object deserialization based on Corvus.ContentHandling. +/// +public class RegisteredContentType2 +{ + public const string RegisteredContentType = "registeredContentType2"; +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs new file mode 100644 index 000000000..037dc7dd9 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType1.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +/// +/// A class used to test object deserialization based on . +/// +public class RegisteredDiscriminatedType1 +{ +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs new file mode 100644 index 000000000..746e7d022 --- /dev/null +++ b/Solutions/Menes.Specs/Fakes/RegisteredDiscriminatedType2.cs @@ -0,0 +1,12 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Fakes; + +/// +/// A class used to test object deserialization based on . +/// +public class RegisteredDiscriminatedType2 +{ +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature new file mode 100644 index 000000000..dc952c30e --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ArrayInputParsing.feature @@ -0,0 +1,213 @@ +@perScenarioContainer + +Feature: Array Input Parsing + In order to implement a web API + As a developer + I want to be able to specify JSON array inputs within the OpenAPI specification and have corresponding input parameters or bodies deserialized and validated + +Scenario: Array body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '[1,2,3,4,5]' as the request body + Then the parameter body should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with items of simple type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' + When I try to parse the query value '[1,2,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer', and the default value for the parameter is '[1,2,3,4,5]' + When I try to parse the default value + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the query value '[[1],[2,3],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + + +Scenario: Array body with items of object type with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' as the request body + Then the parameter body should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + +Scenario: Array parameter with items of object type with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '[{"id": 123, "name": "Ed"}, {"id": 456, "name": "Ian"}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"id":123,"name":"Ed"},{"id":456,"name":"Ian"}] of type System.String + +Scenario: Array parameter with items of object type with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + + +Scenario Outline: Incorrect body values with array simple types + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | true | + | null | + | 42 | + | 42.0 | + | "text" | + | [1,2,3.0] | + | ["1", "2"] | + | [true, false] | + +Scenario Outline: Incorrect body values with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} | + | [{"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}}] | + | [{"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}}] | + | null | + | true | + | false | + | 42 | + | 42.0 | + | "text" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the query value '' as the parameter 'openApiObject' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":true} | + | {"id":123,"name":null} | + | {"id":123,"name":456} | + | {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter values with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the query value '' as the parameter 'openApiObjectWithComplexProperties' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":null} | + # You'd expect these next 3 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"id":123,"name":true} | + #| {"id":123,"name":456} | + #| {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + + +Scenario Outline: Incorrect parameter defaults with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + # You'd expect these next 5 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature new file mode 100644 index 000000000..f2aa95a5b --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanInputParsing.feature @@ -0,0 +1,181 @@ +@perScenarioContainer + +Feature: Boolean Input Parsing + In order to implement a web API + As a developer + I want to be able to specify boolean inputs within the OpenAPI specification and have corresponding input parameters or bodies deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'boolean', and format '' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Boolean + + Examples: + | Value | ExpectedResult | + | true | true | + | false | false | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the parameter 'openApiBoolean' + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | true | true | + | path | false | false | + | query | true | true | + | query | false | false | + | header | true | true | + | header | false | false | + | cookie | true | true | + | cookie | false | false | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', format '' and default value '' + When I try to parse the default value + Then the parameter openApiBoolean should be of type System.Boolean + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | true | true | + | query | false | false | + | header | true | true | + | header | false | false | + | cookie | true | true | + | cookie | false | false | + +Scenario: Array body with items of boolean type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'boolean' + When I try to parse the value '[true,false,true,true,false]' as the request body + Then the parameter body should be [true,false,true,true,false] of type System.String + +Scenario: Array parameter with items of boolean type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'boolean' + When I try to parse the query value '[true,false,true,true,false]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [true,false,true,true,false] of type System.String + +Scenario: Array parameter with items of boolean type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'boolean', and the default value for the parameter is '[true,false,true,true,false]' + When I try to parse the default value + Then the parameter openApiArray should be [true,false,true,true,false] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'boolean' + When I try to parse the value '[[true],[false,true],[true,false,true]]' as the request body + Then the parameter body should be [[true],[false,true],[true,false,true]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'boolean' + When I try to parse the query value '[[true],[false,true],[true,false,true]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[true],[false,true],[true,false,true]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'boolean', and the default value for the parameter is '[[true],[false,true],[true,false,true]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[true],[false,true],[true,false,true]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the value '[{"v1":true,"v2":false},{"v1":false,"v2":true}]' as the request body + Then the parameter body should be [{"v1":true,"v2":false},{"v1":false,"v2":true}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the query value '[{"v1":true,"v2":false},{"v1":false,"v2":true}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":true,"v2":false},{"v1":false,"v2":true}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }', and the default value for the parameter is '[{"v1":true,"v2":false},{"v1":false,"v2":true}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":true,"v2":false},{"v1":false,"v2":true}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the value '{"v1":true,"v2":false}' as the request body + Then the parameter body should be {"v1":true,"v2":false} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }' + When I try to parse the query value '{"v1":true,"v2":false}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1":true,"v2":false} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "boolean" }, "v2": {"type": "boolean"} }', and the default value for the parameter is '{"v1":true,"v2":false}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":true,"v2":false} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "boolean" } }, "details": {"type": "object", "properties": { "v1": { "type": "boolean" }, "v2": { "type": "boolean" } } } }' + When I try to parse the value '{"values":[true,false],"details":{"v1":true,"v2":false}}' as the request body + Then the parameter body should be {"values":[true,false],"details":{"v1":true,"v2":false}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "boolean" } }, "details": {"type": "object", "properties": { "v1": { "type": "boolean" }, "v2": { "type": "boolean" } } } }' + When I try to parse the query value '{"values":[true,false],"details":{"v1":true,"v2":false}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[true,false],"details":{"v1":true,"v2":false}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "boolean" } }, "details": {"type": "object", "properties": { "v1": { "type": "boolean" }, "v2": { "type": "boolean" } } } }', and the default value for the parameter is '{"values":[true,false],"details":{"v1":true,"v2":false}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[true,false],"details":{"v1":true,"v2":false}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'boolean', and format '' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a boolean" | + | thisisnotevenvalidjson | + | null | + | 42 | + | 9223372036854775807 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', and format '' + When I try to parse the value '' as the parameter 'openApiBoolean' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a boolean | + | query | null | + | query | 42 | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | header | This is certainly not a boolean | + | cookie | This+is+certainly+not+a+boolean | + | path | This+is+certainly+not+a+boolean | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiBoolean', type 'boolean', format '' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a boolean" | + | query | thisisnotevenvalidjson | + | query | null | + | query | 42 | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # String values of "true" and "false" are incorrect. The JSON true and false values should be used. + | query | "true" | + | query | "false" | + | header | "This is certainly not a boolean" | + | cookie | "This is certainly not a boolean" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature new file mode 100644 index 000000000..4fc87775c --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/BooleanOutputParsing.feature @@ -0,0 +1,17 @@ +@perScenarioContainer + +Feature: Boolean Output Parsing + In order to implement a web API + As a developer + I want to be able to specify boolean values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'boolean', and format '' + When I try to build a response body from the value '' of type 'System.Boolean' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | true | true | + | false | false | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature new file mode 100644 index 000000000..59d6b6fe4 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayInputParsing.feature @@ -0,0 +1,96 @@ +@perScenarioContainer + +Feature: Byte Array Input Parsing + In order to implement a web API + As a developer + I want to be able to specify byte-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'byte' + When I try to parse the value '' as the request body + Then the parameter body should be of type ByteArrayFromBase64String + + Examples: + | Value | ExpectedResult | + | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + | "" | | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'byte' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type ByteArrayFromBase64String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + | query | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + | cookie | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + | header | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'byte' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type ByteArrayFromBase64String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + | query | | | + | cookie | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + | header | "U3dhZ2dlciByb2Nrcw==" | U3dhZ2dlciByb2Nrcw== | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'byte' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not base64" | + | "2017-07-21T17:32:28Z" | + | thisisnotevenvalidjson | + | false | + | 42 | + | 9223372036854775808 | + # Because our current code design doesn't enable validation to make a distinction between + # quoted and unquoted text in request bodies, these values that shouldn't be handled as + # valid (because they are non-string JSON values, and therefore cannot possibly be + # base64-formatted string values) are in fact accepted. (Don't be misled by the fact + # that they don't much look like base64. Lots of strings consisting of only alphanumeric + # values are acceptable as base64 if their length happens to be a multiple of 4.) + #| true | + #| null | + #| 9223372036854775808123123123 | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'byte' + When I try to parse the value '' as the parameter 'openApiString' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not base64 | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a date | + | cookie | This%3Ais+certainly+not+a+date | + | path | This%3Ais+certainly+not+a+date | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'byte' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | ParameterLocation | DefaultValue | + | query | "This:is certainly not base64" | + | query | thisisnotevenvalidjson | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature new file mode 100644 index 000000000..990d03217 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ByteArrayOutputParsing.feature @@ -0,0 +1,17 @@ +@perScenarioContainer + +Feature: Byte Array Output Parsing + In order to implement a web API + As a developer + I want to be able to specify byte-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'byte' + When I try to build a response body from the value '' of type 'ByteArrayFromBase64String' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | U3dhZ2dlciByb2Nrcw== | "U3dhZ2dlciByb2Nrcw==" | + | | "" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature new file mode 100644 index 000000000..477c569aa --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateInputParsing.feature @@ -0,0 +1,101 @@ +@perScenarioContainer + +Feature: Date Input Parsing + In order to implement a web API + As a developer + I want to be able to specify date-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.DateTimeOffset + + Examples: + | Value | ExpectedResult | + | "2017-07-21" | 2017-07-21T00:00:00Z | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', and format 'date' + When I try to parse the value '' as the parameter 'openApiDate' + Then the parameter openApiDate should be of type System.DateTimeOffset + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 2017-07-21 | 2017-07-21T00:00:00Z | + | query | 2017-07-21 | 2017-07-21T00:00:00Z | + | cookie | 2017-07-21 | 2017-07-21T00:00:00Z | + | header | 2017-07-21 | 2017-07-21T00:00:00Z | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', format 'date' and default value '' + When I try to parse the default value + Then the parameter openApiDate should be of type System.DateTimeOffset + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "2017-07-21" | 2017-07-21T00:00:00Z | + | cookie | "2017-07-21" | 2017-07-21T00:00:00Z | + | header | "2017-07-21" | 2017-07-21T00:00:00Z | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a date" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 42 | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + | "2017-07-21T17:32:28Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', and format 'date' + When I try to parse the value '' as the parameter 'openApiDate' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a date | + | query | true | + | query | false | + | query | null | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a date | + | cookie | This+is+certainly+not+a+date | + | path | This+is+certainly+not+a+date | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDate', type 'string', format 'date' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a date" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # I'm not convinced this next one should work, but Microsoft.OpenApi parses it happily, and discards + # the time portion, leaving just the 2017-07-21 date. + #| query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature new file mode 100644 index 000000000..f778a2027 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: Date Output Parsing + In order to implement a web API + As a developer + I want to be able to specify date values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'date' + When I try to build a response body from the value '' of type 'System.DateTimeOffset' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 2017-07-21T00:00:00Z | "2017-07-21" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature new file mode 100644 index 000000000..81936dbee --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeInputParsing.feature @@ -0,0 +1,106 @@ +@perScenarioContainer + +Feature: DateTime Input Parsing + In order to implement a web API + As a developer + I want to be able to specify date-time-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date-time' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.String + + Examples: + | Value | ExpectedResult | + | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28Z | + | "2017-07-21T17:32:28" | 2017-07-21T17:32:28 | + | "2017-07-21" | 2017-07-21 | + | "20170721" | 20170721 | + | "2017-07" | 2017-07 | + | "2017" | 2017 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', and format 'date-time' + When I try to parse the value '' as the parameter 'openApiDateTime' + Then the parameter openApiDateTime should be of type System.String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 2017-07-21 | 2017-07-21 | + | query | 2017-07-21 | 2017-07-21 | + | cookie | 2017-07-21 | 2017-07-21 | + | header | 2017-07-21 | 2017-07-21 | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', format 'date-time' and default value '' + When I try to parse the default value + Then the parameter openApiDateTime should be of type System.String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "2017-07-21T17:32:28+00:00" | 2017-07-21T17:32:28+00:00 | + | cookie | "2017-07-21T17:32:28+00:00" | 2017-07-21T17:32:28+00:00 | + | header | "2017-07-21T17:32:28+00:00" | 2017-07-21T17:32:28+00:00 | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'date' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a date" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 42 | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + | "2017-07-21T17:32:28Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', and format 'date' + When I try to parse the value '' as the parameter 'openApiDateTime' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a date | + | query | true | + | query | false | + | query | null | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a date | + | cookie | This+is+certainly+not+a+date | + | path | This+is+certainly+not+a+date | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDateTime', type 'string', format 'date' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a date" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # I'm not convinced this next one should work, but Microsoft.OpenApi parses it happily, and discards + # the time portion, leaving just the 2017-07-21 date. + #| query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature new file mode 100644 index 000000000..845a2f6d0 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DateTimeOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: DateTime Output Parsing + In order to implement a web API + As a developer + I want to be able to specify date-time values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'date-time' + When I try to build a response body from the value '' of type 'System.String' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 2017-07-21T00:00:00Z | "2017-07-21T00:00:00Z" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature new file mode 100644 index 000000000..4a6efd9ae --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleInputParsing.feature @@ -0,0 +1,177 @@ +@perScenarioContainer + +Feature: Double Input Parsing + In order to implement a web API + As a developer + I want to be able to specify double parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'double' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Double + + Examples: + | Value | ExpectedResult | + | 1234 | 1234 | + | 0 | 0 | + | -42 | -42 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | + | -1234.5678987 | -1234.5678987 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiDouble', type 'number', and format 'double' + When I try to parse the value '' as the parameter 'openApiDouble' + Then the parameter openApiDouble should be of type System.Double + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | path | 2147483647 | 2147483647 | + | path | -2147483648 | -2147483648 | + | path | 1234.5 | 1234.5 | + | path | -1234.567 | -1234.567 | + | path | -1234.5678987 | -1234.5678987 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of integer type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'number' and format 'double' + When I try to parse the value '[1,2,3,1234.5,-1234.5678987]' as the request body + Then the parameter body should be [1,2,3,1234.5,-1234.5678987] of type System.String + +Scenario: Array parameter with items of integer type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'double' + When I try to parse the query value '[1,-1234.5678987,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,-1234.5678987,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'double', and the default value for the parameter is '[1.0,2.0,-1234.5678987,4.0,5.0]' + When I try to parse the default value + Then the parameter openApiArray should be [1.0,2.0,-1234.5678987,4.0,5.0] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'number' and format 'double' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'double' + When I try to parse the query value '[[1],[2,-1234.567],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,-1234.567],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'double', and the default value for the parameter is '[[1.0],[2.0,3.0],[4.0,5.0,6.0]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1.0],[2.0,3.0],[4.0,5.0,6.0]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": {"type":"number","format": "double"}, "v2": {"type":"number","format": "double"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }' + When I try to parse the query value '[{"v1":1234.5678987,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1234.5678987,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }', and the default value for the parameter is '[{"v1":1.0,"v2":1234.5678987},{"v1":-3.0,"v2":0.0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1.0,"v2":1234.5678987},{"v1":-3.0,"v2":0.0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }' + When I try to parse the value '{"v1": -42, "v2": 1234.5678987}' as the request body + Then the parameter body should be {"v1": -42, "v2": 1234.5678987} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type":"number","format": "double" }, "v2": {"type":"number","format": "double"} }', and the default value for the parameter is '{"v1":-42.0,"v2":23.0}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42.0,"v2":23.0} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type":"number","format": "double" } }, "details": {"type": "object", "properties": { "v1": { "type":"number","format": "double" }, "v2": { "type":"number","format": "double" } } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type":"number","format": "double" } }, "details": {"type": "object", "properties": { "v1": { "type":"number","format": "double" }, "v2": { "type":"number","format": "double" } } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type":"number","format": "double" } }, "details": {"type": "object", "properties": { "v1": { "type":"number","format": "double" }, "v2": { "type":"number","format": "double" } } } }', and the default value for the parameter is '{"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'double' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a double" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', and format 'double' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a double | + | query | true | + | query | false | + | query | null | + | query | 20170721T173228Z | + | header | This is certainly not a double | + | cookie | This+is+certainly+not+a+double | + | path | This+is+certainly+not+a+double | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', format 'double' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a double" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a double" | + | cookie | "This is certainly not a double" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature new file mode 100644 index 000000000..fc9feb5fe --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/DoubleOutputParsing.feature @@ -0,0 +1,21 @@ +@perScenarioContainer + +Feature: Double Output Parsing + In order to implement a web API + As a developer + I want to be able to specify double values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'number', and format 'double' + When I try to build a response body from the value '' of type 'System.Double' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0.0 | + | 1234 | 1234.0 | + | -1234 | -1234.0 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | + | -1234.5678987 | -1234.5678987 | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature new file mode 100644 index 000000000..96bfb4b0f --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatInputParsing.feature @@ -0,0 +1,175 @@ +@perScenarioContainer + +Feature: Float Input Parsing + In order to implement a web API + As a developer + I want to be able to specify float parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'double' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Double + + Examples: + | Value | ExpectedResult | + | 1234 | 1234 | + | 0 | 0 | + | -42 | -42 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiFloat', type 'number', and format 'float' + When I try to parse the value '' as the parameter 'openApiFloat' + Then the parameter openApiFloat should be of type System.Single + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | path | 2147483647 | 2147483647 | + | path | -2147483648 | -2147483648 | + | path | 1234.5 | 1234.5 | + | path | -1234.567 | -1234.567 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of float type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'number' and format 'float' + When I try to parse the value '[1,2,3,1234.5,-1234.567]' as the request body + Then the parameter body should be [1,2,3,1234.5,-1234.567] of type System.String + +Scenario: Array parameter with items of float type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'float' + When I try to parse the query value '[1,-1234.567,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,-1234.567,3,4,5] of type System.String + +Scenario: Array parameter with items of float type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'number' and format 'float', and the default value for the parameter is '[1.0,2.0,-1234.567,4.0,5.0]' + When I try to parse the default value + Then the parameter openApiArray should be [1.0,2.0,-1234.567,4.0,5.0] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'number' and format 'float' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'float' + When I try to parse the query value '[[1],[2,-1234.567],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,-1234.567],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'number' and format 'float', and the default value for the parameter is '[[1.0],[2.0,3.0],[4.0,5.0,6.0]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1.0],[2.0,3.0],[4.0,5.0,6.0]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the query value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }', and the default value for the parameter is '[{"v1":1.0,"v2":2.0},{"v1":-3.0,"v2":0.0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1.0,"v2":2.0},{"v1":-3.0,"v2":0.0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the value '{"v1": -42, "v2": 23}' as the request body + Then the parameter body should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "number", "format": "float" }, "v2": {"type": "number", "format": "float"} }', and the default value for the parameter is '{"v1":-42.0,"v2":23.0}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42.0,"v2":23.0} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "number", "format": "float" } }, "details": {"type": "object", "properties": { "v1": { "type": "number", "format": "float" }, "v2": { "type": "number", "format": "float" } } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "number", "format": "float" } }, "details": {"type": "object", "properties": { "v1": { "type": "number", "format": "float" }, "v2": { "type": "number", "format": "float" } } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "number", "format": "float" } }, "details": {"type": "object", "properties": { "v1": { "type": "number", "format": "float" }, "v2": { "type": "number", "format": "float" } } } }', and the default value for the parameter is '{"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10.0,22.0],"details":{"v1":0.0,"v2":42.0}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'number', and format 'float' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a float" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', and format 'float' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a float | + | query | true | + | query | false | + | query | null | + | query | 20170721T173228Z | + | header | This is certainly not a float | + | cookie | This+is+certainly+not+a+float | + | path | This+is+certainly+not+a+float | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'number', format 'float' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a float" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a float" | + | cookie | "This is certainly not a float" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature new file mode 100644 index 000000000..a0417f67d --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/FloatOutputParsing.feature @@ -0,0 +1,20 @@ +@perScenarioContainer + +Feature: Float Output Parsing + In order to implement a web API + As a developer + I want to be able to specify float values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'number', and format 'float' + When I try to build a response body from the value '' of type 'System.Single' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0.0 | + | 1234 | 1234.0 | + | -1234 | -1234.0 | + | 1234.5 | 1234.5 | + | -1234.567 | -1234.567 | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature new file mode 100644 index 000000000..553d9bba5 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32InputParsing.feature @@ -0,0 +1,162 @@ +@perScenarioContainer + +Feature: Integer32 Input Parsing + In order to implement a web API + As a developer + I want to be able to specify 32-bit integer parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger32', type 'integer', and format '' + When I try to parse the value '' as the parameter 'openApiInteger32' + Then the parameter openApiInteger32 should be of type System.Int32 + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of integer type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' + When I try to parse the value '[1,2,3,4,5]' as the request body + Then the parameter body should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' + When I try to parse the query value '[1,2,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer', and the default value for the parameter is '[1,2,3,4,5]' + When I try to parse the default value + Then the parameter openApiArray should be [1,2,3,4,5] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' + When I try to parse the query value '[[1],[2,3],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the query value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }', and the default value for the parameter is '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the value '{"v1": -42, "v2": 23}' as the request body + Then the parameter body should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": { "type": "integer" }, "v2": {"type": "integer"} }', and the default value for the parameter is '{"v1":-42,"v2":23}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42,"v2":23} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "integer" } }, "details": {"type": "object", "properties": { "v1": { "type": "integer" }, "v2": { "type": "integer" } } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "integer" } }, "details": {"type": "object", "properties": { "v1": { "type": "integer" }, "v2": { "type": "integer" } } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": { "type": "integer" } }, "details": {"type": "object", "properties": { "v1": { "type": "integer" }, "v2": { "type": "integer" } } } }', and the default value for the parameter is '{"values":[-10,22],"details":{"v1":0,"v2":42}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'integer', and format '' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a integer" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 9223372036854775807 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', and format '' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a integer | + | query | true | + | query | false | + | query | null | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | header | This is certainly not a integer | + | cookie | This+is+certainly+not+a+integer | + | path | This+is+certainly+not+a+integer | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', format '' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a integer" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 9223372036854775807 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a integer" | + | cookie | "This is certainly not a integer" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature new file mode 100644 index 000000000..1fdecca94 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer32OutputParsing.feature @@ -0,0 +1,20 @@ +@perScenarioContainer + +Feature: Integer32 Output Parsing + In order to implement a web API + As a developer + I want to be able to specify integer32 values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'integer', and format '' + When I try to build a response body from the value '' of type 'System.Int32' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0 | + | 1234 | 1234 | + | -1234 | -1234 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature new file mode 100644 index 000000000..56e4ec076 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64InputParsing.feature @@ -0,0 +1,180 @@ +@perScenarioContainer + +Feature: Integer64 Input Parsing + In order to implement a web API + As a developer + I want to be able to specify 64-bit integer parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'integer', and format 'int64' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Int64 + + Examples: + | Value | ExpectedResult | + | 0 | 0 | + | 1234 | 1234 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 9223372036854775807 | 9223372036854775807 | + | -9223372036854775808 | -9223372036854775808 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger64', type 'integer', and format 'int64' + When I try to parse the value '' as the parameter 'openApiInteger64' + Then the parameter openApiInteger64 should be of type System.Int64 + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 1234 | 1234 | + | path | 0 | 0 | + | path | -42 | -42 | + | path | 2147483647 | 2147483647 | + | path | -2147483648 | -2147483648 | + | path | 9223372036854775807 | 9223372036854775807 | + | path | -9223372036854775808 | -9223372036854775808 | + | query | 1234 | 1234 | + | query | 0 | 0 | + | query | -42 | -42 | + | header | 1234 | 1234 | + | header | 0 | 0 | + | header | -42 | -42 | + | cookie | 1234 | 1234 | + | cookie | 0 | 0 | + | cookie | -42 | -42 | + +Scenario: Array body with items of integer type + Given I have constructed the OpenAPI specification with a request body of type array, containing items of type 'integer' and format 'int64' + When I try to parse the value '[1,2,3,9223372036854775807,-9223372036854775808]' as the request body + Then the parameter body should be [1,2,3,9223372036854775807,-9223372036854775808] of type System.String + +Scenario: Array parameter with items of integer type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' and format 'int64' + When I try to parse the query value '[1,-9223372036854775808,3,4,5]' as the parameter 'openApiArray' + Then the parameter openApiArray should be [1,-9223372036854775808,3,4,5] of type System.String + +Scenario: Array parameter with items of integer type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer' and format 'int64', and the default value for the parameter is '[1,2,-9223372036854775808,4,5]' + When I try to parse the default value + Then the parameter openApiArray should be [1,2,-9223372036854775808,4,5] of type System.String + + +Scenario: Array body with items of array type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type 'integer' and format 'int64' + When I try to parse the value '[[1],[2,3],[4,5,6]]' as the request body + Then the parameter body should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' and format 'int64' + When I try to parse the query value '[[1],[2,3],[4,5,6]]' as the parameter 'openApiNestedArray' + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + +Scenario: Array parameter with items of array type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer' and format 'int64', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' + When I try to parse the default value + Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String + + +Scenario: Array body with items of object type + Given I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type": "integer","format":"int64"} }' + When I try to parse the value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the request body + Then the parameter body should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }' + When I try to parse the query value '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' as the parameter 'openApiArrayWithObjectItems' + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + +Scenario: Array parameter with items of object type via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }', and the default value for the parameter is '[{"v1":1,"v2":2},{"v1":-3,"v2":0}]' + When I try to parse the default value + Then the parameter openApiArrayWithObjectItems should be [{"v1":1,"v2":2},{"v1":-3,"v2":0}] of type System.String + + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }' + When I try to parse the value '{"v1": -42, "v2": 23}' as the request body + Then the parameter body should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }' + When I try to parse the query value '{"v1": -42, "v2": 23}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"v1": -42, "v2": 23} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} }', and the default value for the parameter is '{"v1":-42,"v2":23}' + When I try to parse the default value + Then the parameter openApiObject should be {"v1":-42,"v2":23} of type System.String + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "values": { "type": "array", "items": {"type":"integer","format":"int64"} }, "details": {"type": "object", "properties": { "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} } } }' + When I try to parse the value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the request body + Then the parameter body should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": {"type":"integer","format":"int64"} }, "details": {"type": "object", "properties": { "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} } } }' + When I try to parse the query value '{"values":[-10,22],"details":{"v1":0,"v2":42}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "values": { "type": "array", "items": {"type":"integer","format":"int64"} }, "details": {"type": "object", "properties": { "v1": {"type":"integer","format":"int64"}, "v2": {"type":"integer","format":"int64"} } } }', and the default value for the parameter is '{"values":[-10,22],"details":{"v1":0,"v2":42}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"values":[-10,22],"details":{"v1":0,"v2":42}} of type System.String + + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'integer', and format 'int64' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a integer" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', and format 'int64' + When I try to parse the value '' as the parameter 'openApiInteger' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a integer | + | query | true | + | query | false | + | query | null | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | header | This is certainly not a integer | + | cookie | This+is+certainly+not+a+integer | + | path | This+is+certainly+not+a+integer | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiInteger', type 'integer', format 'int64' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a integer" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + # String-typed numbers are incorrect. JSON numeric values should be used. + | query | "42" | + | header | "This is certainly not a integer" | + | cookie | "This is certainly not a integer" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature new file mode 100644 index 000000000..484823cb2 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/Integer64OutputParsing.feature @@ -0,0 +1,22 @@ +@perScenarioContainer + +Feature: Integer64 Output Parsing + In order to implement a web API + As a developer + I want to be able to specify integer64 values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'integer', and format 'int64' + When I try to build a response body from the value '' of type 'System.Int64' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 0 | 0 | + | 1234 | 1234 | + | -1234 | -1234 | + | 2147483647 | 2147483647 | + | -2147483648 | -2147483648 | + | 9223372036854775807 | 9223372036854775807 | + | -9223372036854775808 | -9223372036854775808 | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature new file mode 100644 index 000000000..4d74c78e0 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectInputParsing.feature @@ -0,0 +1,215 @@ +@perScenarioContainer + +Feature: Object Input Parsing + In order to implement a web API + As a developer + I want to be able to specify JSON object inputs within the OpenAPI specification and have corresponding input parameters or bodies deserialized and validated + +Scenario: Object body with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the value '{"id":123,"name":"Ed"}' as the request body + Then the parameter body should be {"id":123,"name":"Ed"} of type System.String + +Scenario: Object parameter with properties of simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the query value '{"id":123,"name":"Ed"}' as the parameter 'openApiObject' + Then the parameter openApiObject should be {"id":123,"name":"Ed"} of type System.String + +Scenario: Object parameter with properties of simple types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '{"id":123,"name":"Ed"}' + When I try to parse the default value + Then the parameter openApiObject should be {"id":123,"name":"Ed"} of type System.String + + +Scenario Outline: Object body with property-based discriminator using configured discriminated types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "objectType": { "type": "string" } }' with 'objectType' as the discriminator + When I try to parse the value '{"objectType":""}' as the request body + Then the parameter body should be of type '' + + Examples: + | Discriminator | ExpectedType | + | registeredDiscriminatedType1 | RegisteredDiscriminatedType1 | + | registeredDiscriminatedType2 | RegisteredDiscriminatedType2 | + +Scenario: Object body with property-based discriminator using registered content type + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "objectType": { "type": "string" } }' with 'objectType' as the discriminator + When I try to parse the value '{"objectType":""}' as the request body + Then the parameter body should be of type '' + + Examples: + | Discriminator | ExpectedType | + | registeredContentType1 | RegisteredContentType1 | + | registeredContentType2 | RegisteredContentType2 | + + +Scenario: Object body with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' as the request body + Then the parameter body should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + +Scenario: Object parameter with properties of complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the query value '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' as the parameter 'openApiObjectWithComplexProperties' + Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + +Scenario: Object parameter with properties of complex types via default + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '{"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}}' + When I try to parse the default value + Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String + + +Scenario Outline: Incorrect body values with properties of simple types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":true} | + | {"id":123,"name":null} | + | {"id":123,"name":456} | + | {"id":123,"name":456.7} | + +Scenario Outline: Incorrect body values with properties of complex types + Given I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | "text" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to parse the query value '' as the parameter 'openApiObject' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":true} | + | {"id":123,"name":null} | + | {"id":123,"name":456} | + | {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter values with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }' + When I try to parse the query value '' as the parameter 'openApiObjectWithComplexProperties' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"id":123.0,"name":"Ed"} | + | {"id":"123","name":"Ed"} | + | {"id":true,"name":"Ed"} | + | {"id":null,"name":"Ed"} | + | {"id":123,"name":null} | + # You'd expect these next 3 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"id":123,"name":true} | + #| {"id":123,"name":456} | + #| {"id":123,"name":456.7} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | + + +Scenario Outline: Incorrect parameter defaults with complex types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + | DefaultValue | + | {"names":["Ed",null],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed",{}],"details":{"age":24,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24.1,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"24","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":"Ian","hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":true,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":null,"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":{},"hairColour":"Brown"}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":null}} | + | {"names":["Ed","Ian"],"details":{"age":24,"hairColour":{}}} | + # You'd expect these next 5 to fail, but Microsoft.OpenApi knows that "name" is supposed to be a string, + # so it "helpfully" converts JSON values of type bool or number to strings in the schema's default + # values. It's not clear whether that's correct. We might need to revisit this when moving to + # Corvus.JsonSchema. + #| {"names":["Ed",42],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed",true],"details":{"age":24,"hairColour":"Brown"}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":1.1}} | + #| {"names":["Ed","Ian"],"details":{"age":24,"hairColour":true}} | + | null | + | true | + | false | + | 42 | + | 42.0 | + | text | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature new file mode 100644 index 000000000..e5d895ea0 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/ObjectOutputParsing.feature @@ -0,0 +1,12 @@ +@perScenarioContainer + +Feature: Oubject Output Parsing + In order to implement a web API + As a developer + I want to be able to specify objects as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario: Object with properties of simple types + Given I have constructed the OpenAPI specification with a response body of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }' + When I try to build a response body from the value '{"id":123,"name":"Ed"}' of type 'ObjectWithIdAndName' + Then the response body should be '{"id":123,"name":"Ed"}' diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature new file mode 100644 index 000000000..a0f7b44af --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/OpenApiDefaultParameterParsing.feature @@ -0,0 +1,97 @@ +@perScenarioContainer + +Feature: OpenApiDefaultParameterParsing + In order to simplify setting values for parameters required for the underlying operation + As a developer + I want to be able to specify default values for those parameters within the OpenAPI specification and have those parameters validated and serialized for use downstream. + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name '', type '', format '' and default value '' + When I try to parse the default value + Then the parameter should be of type + + Examples: + | ParameterLocation | ParameterName | Type | Format | DefaultValue | ExpectedResult | ExpectedResultType | + | query | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | + | query | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | + | query | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | + | query | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | + | query | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | + | query | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | + | query | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | + | query | openApiBoolean | boolean | | true | true | System.Boolean | + | query | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | + | query | openApiInteger | integer | | 1234 | 1234 | System.Int32 | + | query | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | + | query | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | + | query | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | + | header | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | + | header | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | + | header | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | + | header | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | + | header | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | + | header | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | + | header | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | + | header | openApiBoolean | boolean | | true | true | System.Boolean | + | header | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | + | header | openApiInteger | integer | | 1234 | 1234 | System.Int32 | + | header | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | + | header | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | + | header | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | + | cookie | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | + | cookie | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | + | cookie | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | + | cookie | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | + | cookie | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | + | cookie | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | + | cookie | openApiString | string | | "I said \"What the ♻😟¥a is a 'PC'?\"" | I said "What the ♻😟¥a is a 'PC'?" | System.String | + | cookie | openApiBoolean | boolean | | true | true | System.Boolean | + | cookie | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | + | cookie | openApiInteger | integer | | 1234 | 1234 | System.Int32 | + | cookie | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | + | cookie | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | + | cookie | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | + + + +Scenario: Any parameter with null default value + Given I have constructed the OpenAPI specification with a query parameter with name openApiNull, type string, format null and a null default value + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name '', type '', and format '' + When I try to parse the value '' as the parameter '' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | ParameterName | Type | Format | Value | + | query | openApiDate | string | date | "This is certainly not a date" | + | header | openApiDateTime | string | date-time | "20170721T173228Z" | + | cookie | openApiLong | integer | int64 | 9223372036854775808123123123 | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name '', type '', format '' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + # 1st example: + # If we set Type to string and Format to date, the DateConverter will definitely claim responsibility. + # And if we then hand it a thing that's not a DateTimeOffset, it will fail when it tries to cast the JToken + # to a DateTimeOffset. That cast is invoking an explicit conversion, and that throws a FormatException if + # the text isn't a date. + # 2nd example: + # The very non-obvious aspect of this is that although 20170721T173228Z looks a lot like a valid date time, + # it's not one of the acceptable forms for the date-time format. The closest acceptable format would be + # 2017-07-21T17:32:28. I don't know what the rationale is for getting a different exception for that vs + # the first example. They're both just strings that aren't in the expected format, and the only reason I + # can see for getting different exception types is as an artifact of the implementation details - the + # incorrectly formatted DateTimeOffset is detected by Json.NET at the point where we convert a JToken to + # a DateTimeOffset, whereas the incorrectly formatted date-time is detected by the JSON Schema validation. + # Since all three of these are errors caused by an incorrectly formatted string as the default value in + # the OpenAPI spec. So I think it should be an OpenApiSpecificationException in all three cases. + Examples: + | ParameterLocation | ParameterName | Type | Format | DefaultValue | + | query | openApiDate | string | date | "This is certainly not a date" | + | header | openApiDateTime | string | date-time | "20170721T173228Z" | + | cookie | openApiLong | integer | int64 | 9223372036854775808123123123 | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature new file mode 100644 index 000000000..eefaf09ba --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordInputParsing.feature @@ -0,0 +1,96 @@ +@perScenarioContainer + +Feature: Password Input Parsing + In order to implement a web API + As a developer + I want to be able to specify password-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'password' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.String + + Examples: + | Value | ExpectedResult | + | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiPassword', type 'string', and format 'password' + When I try to parse the value '' as the parameter 'openApiPassword' + Then the parameter openApiPassword should be of type System.String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + | query | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + | cookie | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + | header | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiPassword', type 'string', format 'password' and default value '' + When I try to parse the default value + Then the parameter openApiPassword should be of type System.String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + | cookie | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + | header | "myVErySeCurePAsSworD123" | myVErySeCurePAsSworD123 | + +#Scenario Outline: Incorrect body values +# Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'password' +# When I try to parse the value '' as the request body and expect an error +# Then an 'OpenApiBadRequestException' should be thrown +# +# Examples: +# # In principle, values that aren't quoted strings (e.g., true, false, numbers, objects) +# # should all fail because the request body is JSON, and only a string can contain a +# # password. However, IOpenApiConverter requires incoming strings values to have the quotes +# # removed, meaning that it can't tell the difference between "true" (which is a valid +# # relative password) and true (which shouldn't be). When we move to Corvus.JsonSchema, we +# # may be able to fix this, but for, any input body will be accepted as a password. +# | Value | +# | thisisnotevenvalidjson | +# | true | +# | false | +# | null | +# | 42 | +# | 9223372036854775808 | +# | 9223372036854775808123123123 | + +# Note: most input parsing specs have an "Incorrect parameter values" scenario outline. +# But because all strings are acceptable as passwords, there are no invalid inputs. +# And unlike the "Incorrect body values" case, non-string JSON values aren't a factor +# because when it comes to parameters, strings are never quoted. Whereas our inability +# to distinguish between "true" and true in the body case is a side effect of our current +# code design, in this case it's actually an unavoidable feature: when we have, say, +# http://example.com/pet?name=true, the value of the name parameter is not necessarily +# the JSON boolean constant true - the example would look the same if the parameter is +# intended as a string. + + +#Scenario Outline: Incorrect parameter defaults +# Given I have constructed the OpenAPI specification with a parameter with name 'openApiPassword', type 'string', format 'password' and default value '' +# When I try to parse the default value and expect an error +# Then an 'OpenApiSpecificationException' should be thrown +# +# Examples: +# # I had been expecting to be able to produce a failure with these because +# # the value handling doesn't go through the problematic code path that makes +# # it impossible to detect these as wrong when they are in a request body. +# # In these cases, it's Microsoft.OpenApi that parses the default value (because +# # it goes into the OpenAPI spec). However, it turns out that if you state that +# # a parameter type is a string, Microsoft.OpenApi will coerce any non-string +# # default value to a string. So if you specify a default value as the JSON +# # true constant, Microsoft.OpenApi acts as though you'd written the string +# # value "true". It's not clear if this is required by the OpenAPI or JSON +# # schema specifications, or if it's just the library trying to be helpful. +# # If it's the latter, we might want to reinstate these cases once we move +# # to Corvus.JsonSchema. +# | ParameterLocation | DefaultValue | +# | query | thisisnotevenvalidjson | +# | query | true | +# | query | false | +# | query | 42 | +# | query | 9223372036854775808 | +# | query | 9223372036854775808123123123 | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature new file mode 100644 index 000000000..aa4a6185a --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/PasswordOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: Password Output Parsing + In order to implement a web API + As a developer + I want to be able to specify password-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'password' + When I try to build a response body from the value '' of type 'System.String' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | myVErySeCurePAsSworD123 | "myVErySeCurePAsSworD123" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature new file mode 100644 index 000000000..c46a8f544 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/StringInputParsing.feature @@ -0,0 +1,141 @@ +@perScenarioContainer + +Feature: String Input Parsing + In order to implement a web API + As a developer + I want to be able to specify string parameters with an unspecified format within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format '' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.String + + Examples: + | Value | ExpectedResult | + | "simpletext" | simpletext | + | "simple text with space" | simple text with space | + # SpecFlow doesn't pass backslashes through unaltered. The comments for the next few the actual strings + # show the intended strings we're actually testing for. + # "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" + | "I said \\"What is a 'PC'?\\"" | I said "What is a 'PC'?" | + # "I said \\\"What is a 'PC'?\\\"" | I said \"What is a 'PC'?\" + | "I said \\\\\\"What is a 'PC'?\\\\\\"" | I said \\"What is a 'PC'?\\" | + #| "I said \"What the ♻😟¥a is a 'PC'?\"" | I said "What the ♻😟¥a is a 'PC'?" + | "I said \\"What the ♻😟¥a is a 'PC'?\\"" | I said "What the ♻😟¥a is a 'PC'?" | + #| "I said \\\"What the ♻😟¥a is a 'PC'?\"" | I said \"What the ♻😟¥a is a 'PC'?" + | "I said \\\\\\"What the ♻😟¥a is a 'PC'?\\"" | I said \\"What the ♻😟¥a is a 'PC'?" | + | "42" | 42 | + | "0" | 0 | + | "-0.12" | -0.12 | + | "true" | true | + | "false" | false | + | "null" | null | + | "{\\"lookslike\\": \\"object\\", \\"isActually\\": \\"string\\"}" | {"lookslike": "object", "isActually": "string"} | + | "https://myuri.com" | https://myuri.com | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format '' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type System.String + + Examples: + | ParameterLocation | Value | ExpectedResult | + | query | simpletext | simpletext | + | query | simple text with space | simple text with space | + | query | I said "What is a 'PC'?" | I said "What is a 'PC'?" | + | query | I said \\"What is a 'PC'?\\" | I said \\"What is a 'PC'?\\" | + | query | 42 | 42 | + | query | 0 | 0 | + | query | -0.12 | -0.12 | + | query | true | true | + | query | false | false | + | query | null | null | + | query | {"lookslike": "object", "isActually": "string"} | {"lookslike": "object", "isActually": "string"} | + | path | simple+text+with+space | simple+text+with+space | + | cookie | simple+text+with+space | simple+text+with+space | + | header | simple+text+with+space | simple+text+with+space | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format '' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type System.String + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "simpletext" | simpletext | + | query | "simple text with space" | simple text with space | + | query | "I said \\"What is a 'PC'?\\"" | I said "What is a 'PC'?" | + | query | "I said \\"What the ♻😟¥a is a 'PC'?\\"" | I said "What the ♻😟¥a is a 'PC'?" | + | query | "I said \\\\\\"What the ♻😟¥a is a 'PC'?\\"" | I said \\"What the ♻😟¥a is a 'PC'?" | + | query | "42" | 42 | + | query | "0" | 0 | + | query | "-0.12" | -0.12 | + | query | "true" | true | + | query | "false" | false | + | query | "null" | null | + | query | "{\\"lookslike\\": \\"object\\", \\"isActually\\": \\"string\\"}" | {"lookslike": "object", "isActually": "string"} | + | cookie | "simple+text+with+space" | simple+text+with+space | + | header | "simple+text+with+space" | simple+text+with+space | + +#Scenario Outline: Incorrect body values +# Given I have constructed the OpenAPI specification with a request body of type 'string', and format '' +# When I try to parse the value '' as the request body and expect an error +# Then an 'OpenApiBadRequestException' should be thrown +# +# Examples: +# # In principle, values that aren't quoted strings (e.g., true, false, numbers, objects) +# # should all fail because the request body is JSON, and only a quoted string is really a +# # string. However, IOpenApiConverter requires incoming strings values to have the quotes +# # removed, meaning that it can't tell the difference between "true" (which is a valid +# # relative URI) and true (which shouldn't be). When we move to Corvus.JsonSchema, we +# # may be able to fix this, but for, any input body will be accepted as a URI. +# | Value | +# | {"foo":42} | +# | true | +# | false | +# | null | +# | 0 | +# | 1.2 | + +# Note: most input parsing specs have an "Incorrect parameter values" scenario outline. +# But because strings are value strings, there are no invalid inputs. And unlike the +# "Incorrect body values" case, non-string JSON values aren't a factor because when it +# comes to parameters, strings are never quoted. Whereas our inability to distinguish +# between "true" and true in the body case is a side effect of our current code design, +# in this case it's actually an unavoidable feature: when we have, say, +# http://example.com/pet?name=true, the value of the name parameter is not necessarily the +# JSON boolean constant true - the example would look the same if the parameter is intended as a string. + +#Scenario Outline: Incorrect parameter defaults +# Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format '' and default value '' +# When I try to parse the default value and expect an error +# Then an 'OpenApiSpecificationException' should be thrown +# +# Examples: +# # I had been expecting to be able to produce a failure with these because +# # the value handling doesn't go through the problematic code path that makes +# # it impossible to detect these as wrong when they are in a request body. +# # In these cases, it's Microsoft.OpenApi that parses the default value (because +# # it goes into the OpenAPI spec). However, it turns out that if you state that +# # a parameter type is a string, Microsoft.OpenApi will coerce any non-string +# # default value to a string. So if you specify a default value as the JSON +# # true constant, Microsoft.OpenApi acts as though you'd written the string +# # value "true". It's not clear if this is required by the OpenAPI or JSON +# # schema specifications, or if it's just the library trying to be helpful. +# # If it's the latter, we might want to reinstate these cases once we move +# # to Corvus.JsonSchema. +# | ParameterLocation | DefaultValue | +# | query | true | +# | query | false | +# | query | null | +# | query | 42 | +# | query | 9223372036854775808 | +# | query | 9223372036854775808123123123 | +# | query | {"foo":42} | +# | query | true | +# | query | false | +# | query | null | +# | query | 0 | +# | query | 1.2 | +# | header | true | +# | cookie | true | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature new file mode 100644 index 000000000..3f3d09a2c --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriInputParsing.feature @@ -0,0 +1,109 @@ +@perScenarioContainer + +Feature: Uri Input Parsing + In order to implement a web API + As a developer + I want to be able to specify URI-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uri' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Uri + + Examples: + # Note: relative URIs have always been accepted as inputs (although not as outputs). + # It's not clear that this is a correct interpretation of the specification, but it + # is a correct description of what Menes had been doing in the years before adding + # this spec, so it would be a breaking change to stop allowing them. + | Value | ExpectedResult | + | "https://myuri.com" | https://myuri.com | + | "relativeuri" | relativeuri | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'uri' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type System.Uri + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | https%3A%2F%2Fmyuri.com | https://myuri.com | + | query | https://myuri.com | https://myuri.com | + | cookie | https://myuri.com | https://myuri.com | + | header | https://myuri.com | https://myuri.com | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uri' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type System.Uri + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "https://myuri.com" | https://myuri.com | + | cookie | "https://myuri.com" | https://myuri.com | + | header | "https://myuri.com" | https://myuri.com | + +#Scenario Outline: Incorrect body values +# Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uri' +# When I try to parse the value '' as the request body and expect an error +# Then an 'OpenApiBadRequestException' should be thrown +# +# Examples: +# # Note that since relative URIs are acceptable, most strings are valid. In fact I've +# # been unable to find a string that .NET won't accept. +# # In principle, values that aren't quoted strings (e.g., true, false, numbers, objects) +# # should all fail because the request body is JSON, and only a string can contain a +# # URI. However, IOpenApiConverter requires incoming strings values to have the quotes +# # removed, meaning that it can't tell the difference between "true" (which is a valid +# # relative URI) and true (which shouldn't be). When we move to Corvus.JsonSchema, we +# # may be able to fix this, but for, any input body will be accepted as a URI. +# | Value | +# | {"foo":42} | +# | true | +# | false | +# | null | +# | 0 | +# | 1.2 | + +# Note: most input parsing specs have an "Incorrect parameter values" scenario outline. +# But because .NET seems to accept all strings as URIs (when relative URIs are allowed) +# there are no invalid inputs. And unlike the "Incorrect body values" case, non-string +# JSON values aren't a factor because when it comes to parameters, strings are never +# quoted. Whereas our inability to distinguish between "true" and true in the body case +# is a side effect of our current code design, in this case it's actually an unavoidable +# feature: when we have, say, http://example.com/pet?name=true, the value of the name +# parameter is not necessarily the JSON boolean constant true - the example would look +# the same if the parameter is intended as a string. + +#Scenario Outline: Incorrect parameter defaults +# Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uri' and default value '' +# When I try to parse the default value and expect an error +# Then an 'OpenApiSpecificationException' should be thrown +# +# Examples: +# # I had been expecting to be able to produce a failure with these because +# # the value handling doesn't go through the problematic code path that makes +# # it impossible to detect these as wrong when they are in a request body. +# # In these cases, it's Microsoft.OpenApi that parses the default value (because +# # it goes into the OpenAPI spec). However, it turns out that if you state that +# # a parameter type is a string, Microsoft.OpenApi will coerce any non-string +# # default value to a string. So if you specify a default value as the JSON +# # true constant, Microsoft.OpenApi acts as though you'd written the string +# # value "true". It's not clear if this is required by the OpenAPI or JSON +# # schema specifications, or if it's just the library trying to be helpful. +# # If it's the latter, we might want to reinstate these cases once we move +# # to Corvus.JsonSchema. +# | ParameterLocation | DefaultValue | +# | query | true | +# | query | false | +# | query | null | +# | query | 42 | +# | query | 9223372036854775808 | +# | query | 9223372036854775808123123123 | +# | query | {"foo":42} | +# | query | true | +# | query | false | +# | query | null | +# | query | 0 | +# | query | 1.2 | +# | header | true | +# | cookie | true | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature new file mode 100644 index 000000000..c15825711 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UriOutputParsing.feature @@ -0,0 +1,20 @@ +@perScenarioContainer + +Feature: Uri Output Parsing + In order to implement a web API + As a developer + I want to be able to specify uri-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'uri' + When I try to build a response body from the value '' of type 'System.Uri' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | https://myuri.com | "https://myuri.com" | + # Note: although Menes has always supported relative URIs as inputs, it does not allow them + # as outputs. It seems likely that one of these facts is a bug, but it's not currently clear + # which. We will most likely resolve this when we move over to Corvus.JsonSchema. + #| relativeuri | "relativeuri" | diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature new file mode 100644 index 000000000..8b9daa739 --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidInputParsing.feature @@ -0,0 +1,99 @@ +@perScenarioContainer + +Feature: Uuid Input Parsing + In order to implement a web API + As a developer + I want to be able to specify uuid-formatted string parameters within the OpenAPI specification and have corresponding input parameters deserialized and validated + +Scenario Outline: Body with valid values for simple types + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uuid' + When I try to parse the value '' as the request body + Then the parameter body should be of type System.Guid + + Examples: + | Value | ExpectedResult | + | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + +Scenario Outline: Parameters with valid values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'uuid' + When I try to parse the value '' as the parameter 'openApiString' + Then the parameter openApiString should be of type System.Guid + + Examples: + | ParameterLocation | Value | ExpectedResult | + | path | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | query | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | cookie | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | header | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | + +Scenario Outline: Parameters with valid default values for simple types + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uuid' and default value '' + When I try to parse the default value + Then the parameter openApiString should be of type System.Guid + + Examples: + | ParameterLocation | DefaultValue | ExpectedResult | + | query | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | cookie | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + | header | "9b7d63fb-1689-4697-9571-00d10b873d78" | 9b7d63fb-1689-4697-9571-00d10b873d78 | + +Scenario Outline: Incorrect body values + Given I have constructed the OpenAPI specification with a request body of type 'string', and format 'uuid' + When I try to parse the value '' as the request body and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | Value | + | "This is certainly not a guid" | + | thisisnotevenvalidjson | + | true | + | false | + | null | + | 42 | + | 9223372036854775808 | + | 9223372036854775808123123123 | + | "20170721T173228Z" | + | "2017-07-21T17:32:28Z" | + +Scenario Outline: Incorrect parameter values + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', and format 'uuid' + When I try to parse the value '' as the parameter 'openApiString' and expect an error + Then an 'OpenApiBadRequestException' should be thrown + + Examples: + | ParameterLocation | Value | + | query | This is certainly not a guid | + | query | true | + | query | false | + | query | null | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | 20170721T173228Z | + | query | 2017-07-21T17:32:28Z | + | header | This is certainly not a guid | + | cookie | This+is+certainly+not+a+guid | + | path | This+is+certainly+not+a+guid | + +Scenario Outline: Incorrect parameter defaults + Given I have constructed the OpenAPI specification with a parameter with name 'openApiString', type 'string', format 'uuid' and default value '' + When I try to parse the default value and expect an error + Then an 'OpenApiSpecificationException' should be thrown + + Examples: + # Not doing path, because it doesn't really make sense to provide a default - we're only going to match + # the path template if all parts are present. (E.g., if the path is /pets/{petId}, we don't expect /pets/ + # to match. + | ParameterLocation | DefaultValue | + | query | "This is certainly not a guid" | + | query | thisisnotevenvalidjson | + | query | true | + | query | false | + | query | 42 | + | query | 9223372036854775808 | + | query | 9223372036854775808123123123 | + | query | "20170721T173228Z" | + | query | "2017-07-21T17:32:28Z" | + | query | "42" | + | header | "This is certainly not a date" | + | cookie | "This is certainly not a date" | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature new file mode 100644 index 000000000..6379d59af --- /dev/null +++ b/Solutions/Menes.Specs/Features/JsonTypeConversion/UuidOutputParsing.feature @@ -0,0 +1,16 @@ +@perScenarioContainer + +Feature: Uuid Output Parsing + In order to implement a web API + As a developer + I want to be able to specify uuid-formatted string values as or in response bodies within the OpenAPI specification and have corresponding response bodies deserialized and validated + + +Scenario Outline: Valid values for simple types + Given I have constructed the OpenAPI specification with a response body of type 'string', and format 'uuid' + When I try to build a response body from the value '' of type 'System.Uri' + Then the response body should be '' + + Examples: + | Value | ExpectedResult | + | 9b7d63fb-1689-4697-9571-00d10b873d78 | "9b7d63fb-1689-4697-9571-00d10b873d78" | diff --git a/Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature b/Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature deleted file mode 100644 index abbd6d7ca..000000000 --- a/Solutions/Menes.Specs/Features/OpenApiDefaultParameterParsing.feature +++ /dev/null @@ -1,94 +0,0 @@ -@perScenarioContainer - -Feature: OpenApiDefaultParameterParsing - In order to simplify setting values for parameters required for the underlying operation - As a developer - I want to be able to specify default values for those parameters within the OpenAPI specification and have those parameters validated and serialized for use downstream. - -Scenario Outline: Parameters with valid values for simple types - Given I have constructed the OpenAPI specification with a parameter with name , type , format and default value - When I try to parse the default value - Then the parameter should be of type - - Examples: - | ParameterLocation | ParameterName | Type | Format | DefaultValue | ExpectedResult | ExpectedResultType | - | query | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | - | query | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | - | query | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | - | query | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | - | query | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | - | query | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | - | query | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | - | query | openApiBoolean | boolean | | true | true | System.Boolean | - | query | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | - | query | openApiInteger | integer | | 1234 | 1234 | System.Int32 | - | query | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | - | query | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | - | query | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | - | header | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | - | header | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | - | header | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | - | header | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | - | header | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | - | header | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | - | header | openApiString | string | | "I said \"What is a 'PC'?\"" | I said "What is a 'PC'?" | System.String | - | header | openApiBoolean | boolean | | true | true | System.Boolean | - | header | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | - | header | openApiInteger | integer | | 1234 | 1234 | System.Int32 | - | header | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | - | header | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | - | header | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | - | cookie | openApiDate | string | date | 2017-07-21 | 2017-07-21T00:00:00Z | System.DateTimeOffset | - | cookie | openApiDateTime | string | date-time | "2017-07-21T17:32:28Z" | 2017-07-21T17:32:28+00:00 | System.String | - | cookie | openApiPassword | string | password | myVErySeCurePAsSworD123 | myVErySeCurePAsSworD123 | System.String | - | cookie | openApiByte | string | byte | U3dhZ2dlciByb2Nrcw== | U3dhZ2dlciByb2Nrcw== | ByteArrayFromBase64String | - | cookie | openApiString | string | uuid | 9b7d63fb-1689-4697-9571-00d10b873d78 | 9b7d63fb-1689-4697-9571-00d10b873d78 | System.Guid | - | cookie | openApiString | string | uri | "https://myuri.com" | https://myuri.com | System.Uri | - | cookie | openApiString | string | | "I said \"What the ♻😟¥a is a 'PC'?\"" | I said "What the ♻😟¥a is a 'PC'?" | System.String | - | cookie | openApiBoolean | boolean | | true | true | System.Boolean | - | cookie | openApiLong | integer | int64 | 9223372036854775807 | 9223372036854775807 | System.Int64 | - | cookie | openApiInteger | integer | | 1234 | 1234 | System.Int32 | - | cookie | openApiNumber | number | | 1234.5 | 1234.5 | System.Double | - | cookie | openApiFloat | number | float | 1234.5 | 1234.5 | System.Single | - | cookie | openApiDouble | number | double | 1234.5678 | 1234.5678 | System.Double | - -Scenario: Array parameter with items of simple type - Given I have constructed the OpenAPI specification with a parameter with name 'openApiArray', of type array, containing items of type 'integer', and the default value for the parameter is '[1,2,3,4,5]' - When I try to parse the default value - Then the parameter openApiArray should be [1,2,3,4,5] of type System.String - -Scenario: Array parameter with items of array type - Given I have constructed the OpenAPI specification with a parameter with name 'openApiNestedArray', of type array, containing items which are arrays themselves with item type 'integer', and the default value for the parameter is '[[1],[2,3],[4,5,6]]' - When I try to parse the default value - Then the parameter openApiNestedArray should be [[1],[2,3],[4,5,6]] of type System.String - -Scenario: Array parameter with items of object type - Given I have constructed the OpenAPI specification with a parameter with name 'openApiArrayWithObjectItems', of type array, containing items which are objects which has the property structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '[{"id": 123, "name": "Ed"}, {"id": 456, "name": "Ian"}]' - When I try to parse the default value - Then the parameter openApiArrayWithObjectItems should be [{"id":123,"name":"Ed"},{"id":456,"name":"Ian"}] of type System.String - -Scenario: Object parameter with properties of simple types - Given I have constructed the OpenAPI specification with a parameter with name 'openApiObject', of type object, containing properties in the structure '{ "id": { "type": "integer" }, "name": {"type": "string"} }', and the default value for the parameter is '{"id":123, "name": "Ed"}' - When I try to parse the default value - Then the parameter openApiObject should be {"id":123,"name":"Ed"} of type System.String - -Scenario: Object parameter with properties of complex types - Given I have constructed the OpenAPI specification with a parameter with name 'openApiObjectWithComplexProperties', of type object, containing properties in the structure '{ "names": { "type": "array", "items": { "type": "string" } }, "details": {"type": "object", "properties": { "age": { "type": "integer" }, "hairColour": { "type": "string" } } } }', and the default value for the parameter is '{"names": ["Ed","Ian"] , "details": {"age": 24, "hairColour": "Brown"} }' - When I try to parse the default value - Then the parameter openApiObjectWithComplexProperties should be {"names":["Ed","Ian"],"details":{"age":24,"hairColour":"Brown"}} of type System.String - -Scenario: Any parameter with null default value - Given I have constructed the OpenAPI specification with a query parameter with name openApiNull, type string, format null and a null default value - When I try to parse the default value and expect an error - Then an 'OpenApiSpecificationException' should be thrown - -Scenario Outline: Incorrect parameter values - Given I have constructed the OpenAPI specification with a parameter with name , type , format and default value - When I try to parse the default value and expect an error - Then an '' should be thrown - - Examples: - | ParameterLocation | ParameterName | Type | Format | DefaultValue | ExceptionType | - | query | openApiDate | string | date | This is certainly not a date | FormatException | - | header | openApiDateTime | string | date-time | 20170721T173228Z | OpenApiBadRequestException | - | cookie | openApiLong | integer | int64 | 9223372036854775808123123123 | OpenApiSpecificationException | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs b/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs index 03517eef0..4d9250e3f 100644 --- a/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs +++ b/Solutions/Menes.Specs/Steps/LinkCollectionExtensionsSteps.cs @@ -4,10 +4,12 @@ namespace Menes.Specs.Steps { - using System; using Menes.Links; + using Microsoft.OpenApi.Models; + using Moq; + using TechTalk.SpecFlow; [Binding] diff --git a/Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs deleted file mode 100644 index 309842ad5..000000000 --- a/Solutions/Menes.Specs/Steps/OpenApiDefaultParameterParsingSteps.cs +++ /dev/null @@ -1,169 +0,0 @@ -// -// Copyright (c) Endjin Limited. All rights reserved. -// - -namespace Menes.Specs.Steps -{ - using System; - using System.Collections.Generic; - using Corvus.Testing.SpecFlow; - using Menes.Internal; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - using Microsoft.OpenApi.Models; - using Microsoft.OpenApi.Readers; - using NUnit.Framework; - using TechTalk.SpecFlow; - - [Binding] - public class OpenApiDefaultParameterParsingSteps - { - private readonly ScenarioContext scenarioContext; - private IDictionary? parameters; - private Exception? exception; - - public OpenApiDefaultParameterParsingSteps(ScenarioContext scenarioContext) - { - this.scenarioContext = scenarioContext; - } - - [Given("I have constructed the OpenAPI specification with a (.*) parameter with name (.*), type (.*), format (.*) and default value (.*)")] - public void GivenIConstructASimpleParameter( - string parameterLocation, - string parameterName, - string parameterType, - string parameterFormat, - string parameterDefaultValue) - { - //// Build OpenAPI spec object from scratch to mimic reality. Cutting corners by initializing an - //// OpenApiDocument directly removes the ability for the the parameter type to be inferred. Something - //// that this test is trying to cover. - - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items of type '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnArrayParameterWithSimpleItems( - string parameterName, - string arrayItemType, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ type: \"{arrayItemType}\" }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items which are arrays themselves with item type '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnArrayParameterWithArrayItems( - string parameterName, - string nestedArrayItemType, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{nestedArrayItemType}\" }} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items which are objects which has the property structure '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnArrayParameterWithObjectItems( - string parameterName, - string objectProperties, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"object\", \"properties\": {objectProperties} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type object, containing properties in the structure '(.*)', and the default value for the parameter is '(.*)'")] - public void GivenIConstructAnObjectParameterWithSimpleProperties( - string parameterName, - string objectProperties, - string parameterDefaultValue) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"object\", \"properties\": {objectProperties}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [Given("I have constructed the OpenAPI specification with a (.*) parameter with name (.*), type (.*), format (.*) and a null default value")] - public void GivenIConstructAParameterWithANullDefaultValue( - string parameterLocation, - string parameterName, - string parameterType, - string parameterFormat) - { - string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": null, \"nullable\": true }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; - - this.InitializeDocumentProviderAndPathMatcher(openApiSpec); - } - - [When("I try to parse the default value")] - public async System.Threading.Tasks.Task WhenITryToParseTheDefaultValueAsync() - { - IPathMatcher matcher = this.scenarioContext.Get(); - - IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); - - matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); - - var context = new DefaultHttpContext(); - - this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); - } - - [When("I try to parse the default value and expect an error")] - public async System.Threading.Tasks.Task WhenITryToParseTheDefaultValueAndExpectAnErrorAsync() - { - try - { - await this.WhenITryToParseTheDefaultValueAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - this.exception = ex; - } - } - - [Then("the parameter (.*) should be (.*) of type (.*)")] - public void ThenTheSerializedResultShouldBe(string parameterName, string expectedResultAsString, string expectedType) - { - object expectedResult = expectedType switch - { - "ByteArrayFromBase64String" => Convert.FromBase64String(expectedResultAsString), - "System.DateTimeOffset" => DateTimeOffset.Parse(expectedResultAsString), - "System.Guid" => Guid.Parse(expectedResultAsString), - "System.Uri" => new Uri(expectedResultAsString), - _ => Convert.ChangeType(expectedResultAsString, Type.GetType(expectedType)!), - }; - - Assert.AreEqual(expectedResult, this.parameters![parameterName]); - Assert.AreEqual(expectedResult.GetType(), this.parameters![parameterName]!.GetType()); - } - - [Then("an '(.*)' should be thrown")] - public void ThenAnShouldBeThrown(string exceptionType) - { - Assert.IsNotNull(this.exception); - - Assert.AreEqual(exceptionType, this.exception!.GetType().Name); - } - - private void InitializeDocumentProviderAndPathMatcher(string openApiSpec) - { - OpenApiDocument document = new OpenApiStringReader().Read(openApiSpec, out OpenApiDiagnostic _); - - var documentProvider = new OpenApiDocumentProvider(new LoggerFactory().CreateLogger()); - documentProvider.Add(document); - - this.scenarioContext.Set(documentProvider); - - var matcher = new PathMatcher(documentProvider); - - this.scenarioContext.Set(matcher); - } - } -} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs new file mode 100644 index 000000000..8775fc19e --- /dev/null +++ b/Solutions/Menes.Specs/Steps/OpenApiParameterParsingSteps.cs @@ -0,0 +1,1121 @@ +// +// Copyright (c) Endjin Limited. All rights reserved. +// + +namespace Menes.Specs.Steps +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Text; + using System.Threading.Tasks; + + using Corvus.Testing.SpecFlow; + + using Menes.Internal; + using Menes.Specs.Fakes; + + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Microsoft.OpenApi.Models; + using Microsoft.OpenApi.Readers; + + using Newtonsoft.Json; + + using NUnit.Framework; + + using TechTalk.SpecFlow; + + [Binding] + public class OpenApiParameterParsingSteps + { + private readonly ScenarioContext scenarioContext; + private IPathMatcher? matcher; + private IDictionary? parameters; + private Exception? exception; + private string? responseBody; + + public OpenApiParameterParsingSteps(ScenarioContext scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + private IPathMatcher Matcher => this.matcher ?? throw new InvalidOperationException("Matcher not set - test must first create a fake OpenAPI spec"); + + [Given("I have constructed the OpenAPI specification with a request body of type '([^']*)', and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeAndFormat( + string bodyType, string bodyFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "{{bodyType}}", + "format": "{{bodyFormat}}" + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a response body of type '([^']*)', and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithAResponseBodyOfTypeAndFormat( + string bodyType, string bodyFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "Get a pet", + "operationId": "getPet", + "responses": { + "201": { + "description": "A pet", + "content": { + "application/json": { + "schema": { + "type": "{{bodyType}}", + "format": "{{bodyFormat}}" + } + } + } + } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a response body of type object, containing properties in the structure '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithAResponseBodyOfTypeObjectContainingPropertiesInTheStructure( + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "Get a pet", + "operationId": "getPet", + "responses": { + "200": { + "description": "A pet", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a ([^ ]*) parameter with name '([^']*)', type '([^']*)', and format '([^']*)'")] + public void GivenIConstructAnOpenApiSpecWithSimpleParameter( + string parameterLocation, + string parameterName, + string parameterType, + string parameterFormat) + { + //// Build OpenAPI spec object from scratch to mimic reality. Cutting corners by initializing an + //// OpenApiDocument directly removes the ability for the the parameter type to be inferred. Something + //// that this test is trying to cover. + + string path = parameterLocation == "path" ? $"/pets/{{{parameterName}}}" : "/pets"; + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "{{path}}": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "{{parameterLocation}}", + "schema": { + "type": "{{parameterType}}", + "format": "{{parameterFormat}}", + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a ([^ ]*) parameter with name '([^']*)', type '([^']*)', format '([^']*)' and default value '(.*)'")] + public void GivenIConstructAnOpenApiSpecWithSimpleParameterWithDefault( + string parameterLocation, + string parameterName, + string parameterType, + string parameterFormat, + string parameterDefaultValue) + { + //// Build OpenAPI spec object from scratch to mimic reality. Cutting corners by initializing an + //// OpenApiDocument directly removes the ability for the the parameter type to be inferred. Something + //// that this test is trying to cover. + + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items of type '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsOfType(string arrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + type: "{{arrayItemType}}" + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items of type '([^']*)' and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsOfTypeAndFormat( + string arrayItemType, string arrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "{{arrayItemType}}", + "format": "{{arrayItemFormat}}" + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItems( + string parameterName, + string arrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + type: "{{arrayItemType}}" + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)' and format '([^']*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItemsWithFormat( + string parameterName, + string arrayItemType, + string arrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "{{arrayItemType}}", + "format": "{{arrayItemFormat}}" + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItemsWithDefault( + string parameterName, + string arrayItemType, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ type: \"{arrayItemType}\" }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items of type '([^']*)' and format '([^']*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnArrayParameterWithSimpleItemsWithFormatWithDefault( + string parameterName, + string arrayItemType, + string arrayItemFormat, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{arrayItemType}\", \"format\": \"{arrayItemFormat}\" }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsWhichAreArraysThemselvesWithItemType( + string nestedArrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}" + } + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items which are arrays themselves with item type '([^']*)' and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsWhichAreArraysThemselvesWithItemTypeAndFormat( + string nestedArrayItemType, + string nestedArrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}", + "format": "{{nestedArrayItemFormat}}" + } + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)'")] + public void GivenIConstructAnArrayParameterWithArrayItems( + string parameterName, + string nestedArrayItemType) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}" + } + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)' and format '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithAParameterWithNameOfTypeArrayContainingItemsWhichAreArraysThemselvesWithItemTypeAndFormat( + string parameterName, + string nestedArrayItemType, + string nestedArrayItemFormat) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "{{nestedArrayItemType}}", + "format": "{{nestedArrayItemFormat}}" + } + } + } + } + ], + "responses": { + "200": { "description": "OK" } + } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)', and the default value for the parameter is '([^']*)'")] + public void GivenIConstructAnArrayParameterWithArrayItemsWithDefault( + string parameterName, + string nestedArrayItemType, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{nestedArrayItemType}\" }} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are arrays themselves with item type '([^']*)' and format '([^']*)', and the default value for the parameter is '([^']*)'")] + public void GivenIConstructAnArrayParameterWithArrayItemsWithFormatWithDefault( + string parameterName, + string nestedArrayItemType, + string nestedArrayItemFormat, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"{nestedArrayItemType}\", \"format\": \"{nestedArrayItemFormat}\" }} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type array, containing items which are objects which has the property structure '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeArrayContainingItemsWhichAreObjectsWhichHasThePropertyStructure( + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type array, containing items which are objects which has the property structure '([^']*)'")] + public void GivenIConstructAnArrayParameterWithObjectItems( + string parameterName, + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + ], + "responses": { "200": { "description": "OK" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type array, containing items which are objects which has the property structure '(.*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnArrayParameterWithObjectItemsWithDefault( + string parameterName, + string objectProperties, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"array\", \"items\": {{ \"type\": \"object\", \"properties\": {objectProperties} }}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '([^']*)'")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeObjectContainingPropertiesInTheStructure( + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {{objectProperties}} + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a request body of type object, containing properties in the structure '([^']*)' with '([^']*)' as the discriminator")] + public void GivenIHaveConstructedTheOpenAPISpecificationWithARequestBodyOfTypeObjectContainingPropertiesInTheStructureWithDiscriminator( + string objectProperties, + string discriminator) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { + "title": "Swagger Petstore (Simple)", + "version": "1.0.0" + }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "post": { + "summary": "Create a pet", + "operationId": "createPets", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {{objectProperties}}, + "discriminator": { "propertyName": "{{discriminator}}" } + } + } + } + }, + "responses": { "201": { "description": "Created" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '([^']*)', of type object, containing properties in the structure '([^']*)'")] + public void GivenIConstructAnObjectParameterWithSimpleProperties( + string parameterName, + string objectProperties) + { + string openApiSpec = $$""" + { + "openapi": "3.0.1", + "info": { "title": "Swagger Petstore (Simple)", "version": "1.0.0" }, + "servers": [ { "url": "http://petstore.swagger.io/api" } ], + "paths": { + "/pets": { + "get": { + "summary": "List all pets", + "operationId": "listPets", + "parameters": [ + { + "name": "{{parameterName}}", + "in": "query", + "schema": { + "type": "object", + "properties": {{objectProperties}} + } + } + ], + "responses": { "200": { "description": "OK" } } + } + } + } + } + """; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a parameter with name '(.*)', of type object, containing properties in the structure '(.*)', and the default value for the parameter is '(.*)'")] + public void GivenIConstructAnObjectParameterWithSimplePropertiesWithDefault( + string parameterName, + string objectProperties, + string parameterDefaultValue) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"query\", \"schema\": {{ \"type\": \"object\", \"properties\": {objectProperties}, \"default\": {parameterDefaultValue} }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [Given("I have constructed the OpenAPI specification with a (.*) parameter with name (.*), type (.*), format (.*) and a null default value")] + public void GivenIConstructAParameterWithANullDefaultValue( + string parameterLocation, + string parameterName, + string parameterType, + string parameterFormat) + { + string openApiSpec = $"{{ \"openapi\": \"3.0.1\", \"info\": {{ \"title\": \"Swagger Petstore (Simple)\", \"version\": \"1.0.0\" }}, \"servers\": [ {{ \"url\": \"http://petstore.swagger.io/api\" }} ], \"paths\": {{ \"/pets\": {{ \"get\": {{ \"summary\": \"List all pets\", \"operationId\": \"listPets\", \"parameters\": [ {{ \"name\": \"{parameterName}\", \"in\": \"{parameterLocation}\", \"schema\": {{ \"type\": \"{parameterType}\", \"format\": \"{parameterFormat}\", \"default\": null, \"nullable\": true }} }} ], \"responses\": {{ \"200\": {{ \"description\": \"OK\" }} }} }} }} }} }}"; + + this.InitializeDocumentProviderAndPathMatcher(openApiSpec); + } + + [When("I try to parse the default value")] + public async Task WhenITryToParseTheDefaultValueAsync() + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the default value and expect an error")] + public async Task WhenITryToParseTheDefaultValueAndExpectAnErrorAsync() + { + try + { + await this.WhenITryToParseTheDefaultValueAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the value '(.*)' as the request body")] + public async Task WhenITryToParseTheValueAsTheRequestBody(string body) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "POST", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body)); + context.Request.Headers.ContentType = "application/json"; + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the value '(.*?)' as the request body and expect an error")] + public async Task WhenITryToParseTheValueAsTheRequestBodyAndExpectAnError(string body) + { + try + { + await this.WhenITryToParseTheValueAsTheRequestBody(body).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the path value '(.*?)' as the parameter '([^']*)'")] + public async Task WhenITryToParseThePathValue(string value, string unusedParameterName) + { + _ = unusedParameterName; + + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + string path = $"/pets/{value}"; + this.Matcher.FindOperationPathTemplate(path, "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + DefaultHttpContext context = new(); + context.Request.Path = path; + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the path value '(.*?)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseThePathValueAndExpectAnErrorAsync(string value, string unusedParameterName) + { + try + { + await this.WhenITryToParseThePathValue(value, unusedParameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the query value '(.*?)' as the parameter '([^']*)'")] + public async Task WhenITryToParseTheQueryValue( + string value, + string parameterName) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Query = new QueryCollection(new Dictionary + { + { parameterName, value }, + }); + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the query value '([^']*)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseTheQueryValueAndExpectAnErrorAsync( + string value, + string parameterName) + { + try + { + await this.WhenITryToParseTheQueryValue(value, parameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the header value '([^']*)' as the parameter '([^']*)'")] + public async Task WhenITryToParseTheHeaderValue( + string value, + string parameterName) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Headers.Add(parameterName, value); + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the header value '([^']*)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseTheHeaderValueAndExpectAnErrorAsync( + string value, + string parameterName) + { + try + { + await this.WhenITryToParseTheHeaderValue(value, parameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to parse the cookie value '([^']*)' as the parameter '([^']*)'")] + public async Task WhenITryToParseTheCookieValue( + string value, + string parameterName) + { + IOpenApiParameterBuilder builder = ContainerBindings.GetServiceProvider(this.scenarioContext).GetRequiredService>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + + var context = new DefaultHttpContext(); + context.Request.Cookies = new FakeCookieCollection() + { + { parameterName, value }, + }; + + this.parameters = await builder.BuildParametersAsync(context.Request, operationPathTemplate!).ConfigureAwait(false); + } + + [When("I try to parse the cookie value '([^']*)' as the parameter '([^']*)' and expect an error")] + public async Task WhenITryToParseTheCookieValueAndExpectAnErrorAsync( + string value, + string parameterName) + { + try + { + await this.WhenITryToParseTheCookieValue(value, parameterName).ConfigureAwait(false); + } + catch (Exception ex) + { + this.exception = ex; + } + } + + [When("I try to build a response body from the value '([^']*)' of type '([^']*)'")] + public void WhenITryToBuildAResponseBodyFromTheValueOfTypeSystem_Boolean( + string valueAsString, string valueType) + { + object value = GetResultFromStringAndType(valueAsString, valueType); + + IEnumerable> builders = ContainerBindings.GetServiceProvider(this.scenarioContext). + GetServices>(); + + this.Matcher.FindOperationPathTemplate("/pets", "GET", out OpenApiOperationPathTemplate? operationPathTemplate); + OpenApiOperation operation = operationPathTemplate!.Operation; + + IHttpResponseResult? result = null; + foreach (IResponseOutputBuilder builder in builders) + { + if (builder.CanBuildOutput(value, operation)) + { + result = builder.BuildOutput(value, operation); + break; + } + } + + var context = new DefaultHttpContext(); + context.Response.Body = new MemoryStream(); + result!.ExecuteResultAsync(context.Response); + context.Response.Body.Position = 0; + using StreamReader sr = new(context.Response.Body); + this.responseBody = sr.ReadToEnd(); + } + + [Then("the response body should be '([^']*)'")] + public void ThenTheResponseBodyShouldBeTrue(string expectedBody) + { + Assert.AreEqual(expectedBody, this.responseBody); + } + + [Then("the parameter (.*?) should be (.*?) of type (.*)")] + public void ThenTheParameterShouldBe(string parameterName, string expectedResultAsString, string expectedType) + { + object expectedResult = GetResultFromStringAndType(expectedResultAsString, expectedType); + + Assert.AreEqual(expectedResult, this.parameters![parameterName]); + Assert.AreEqual(expectedResult.GetType(), this.parameters![parameterName]!.GetType()); + } + + [Then("the parameter (.*?) should be of type '([^']*)'")] + public void ThenTheParameterBodyShouldBeOfType(string parameterName, string expectedType) + { + Assert.AreEqual(expectedType, this.parameters![parameterName].GetType().Name); + } + + [Then("an '(.*)' should be thrown")] + public void ThenAnShouldBeThrown(string exceptionType) + { + Assert.IsNotNull(this.exception); + + Assert.AreEqual(exceptionType, this.exception!.GetType().Name); + } + + private static object GetResultFromStringAndType(string expectedResultAsString, string expectedType) + { + return expectedType switch + { + "ByteArrayFromBase64String" => Convert.FromBase64String(expectedResultAsString), + "System.DateTimeOffset" => DateTimeOffset.Parse(expectedResultAsString), + "System.Guid" => Guid.Parse(expectedResultAsString), + "System.Uri" => new Uri(expectedResultAsString, UriKind.RelativeOrAbsolute), + "ObjectWithIdAndName" => JsonConvert.DeserializeObject(expectedResultAsString)!, + _ => Convert.ChangeType(expectedResultAsString, Type.GetType(expectedType)!), + }; + } + + private void InitializeDocumentProviderAndPathMatcher(string openApiSpec) + { + OpenApiDocument document = new OpenApiStringReader().Read(openApiSpec, out OpenApiDiagnostic _); + + var documentProvider = new OpenApiDocumentProvider(new LoggerFactory().CreateLogger()); + documentProvider.Add(document); + + this.scenarioContext.Set(documentProvider); + + this.matcher = new PathMatcher(documentProvider); + } + + private class FakeCookieCollection : IRequestCookieCollection + { + private readonly Dictionary cookies = new(); + + public int Count => this.cookies.Count; + + public ICollection Keys => this.cookies.Keys; + + public string? this[string key] => this.TryGetValue(key, out string? cookie) ? cookie : null; + + public void Add(string key, string value) + { + this.cookies.Add(key, value); + } + + public bool ContainsKey(string key) => this.cookies.ContainsKey(key); + + public IEnumerator> GetEnumerator() => this.cookies.GetEnumerator(); + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out string? value) => this.cookies.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs index d4c55f83f..f10d90c05 100644 --- a/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs +++ b/Solutions/Menes.Specs/Steps/OpenApiValidationSteps.cs @@ -59,7 +59,7 @@ public void WhenIValidateThePayloadAgainstTheSchema() validator.ValidateAndThrow(payload, schema); this.scenarioContext.Set(true, ResultKey); } - catch (OpenApiBadRequestException ex) + catch (OpenApiInvalidFormatException ex) { this.scenarioContext.Set(false, ResultKey); this.scenarioContext.Set(ex, "Exception"); diff --git a/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs index 277fe8cee..1af701c7b 100644 --- a/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs +++ b/Solutions/Menes.Specs/Steps/OpenApiWebLinkResolverSteps.cs @@ -8,15 +8,16 @@ namespace Menes.Specs.Steps using System.Collections.Generic; using System.Linq; using System.Reflection; + using Menes.Internal; using Menes.Links; + using Microsoft.Extensions.Logging; - using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Readers; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; + using NUnit.Framework; + using TechTalk.SpecFlow; using TechTalk.SpecFlow.Assist; diff --git a/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs b/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs index 57f67ce3e..d2bf5ec1c 100644 --- a/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs +++ b/Solutions/Menes.Specs/Steps/TestClasses/OperationInvokerTestContext.cs @@ -7,10 +7,14 @@ namespace Menes.Specs.Steps.TestClasses using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using Idg.AsyncTest; + using Menes.Auditing; using Menes.Internal; + using Microsoft.Extensions.DependencyInjection; + using Moq; /// @@ -43,7 +47,6 @@ public static void AddServices(IServiceCollection services) { var context = new OperationInvokerTestContext(); services.AddSingleton(context); - services.AddSingleton(); services.AddSingleton(context.OperationLocator.Object); services.AddSingleton(new AccessChecker(context)); services.AddSingleton(context.ExceptionMapper.Object); diff --git a/Solutions/Menes.Specs/packages.lock.json b/Solutions/Menes.Specs/packages.lock.json index 04c207a40..69cefb4a1 100644 --- a/Solutions/Menes.Specs/packages.lock.json +++ b/Solutions/Menes.Specs/packages.lock.json @@ -4,15 +4,15 @@ "net6.0": { "Corvus.Testing.SpecFlow.NUnit": { "type": "Direct", - "requested": "[1.6.0, )", - "resolved": "1.6.0", - "contentHash": "V8v7+oRFK99skWq/TmzgiiS6KYQtu/gJXTXep8HKxt3u1rbU7BZFQfPCHiyH7KSAIn+4HEnxqlvCbXUSyRGCAA==", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "mutrleL9V2ftOzcrfBGSe8imboy+p4hxWO+ejNE6TWvBPBdjFlteST/vsHoQwMOVT1VUj4AfXtgSqk5SN+NH4A==", "dependencies": { - "Corvus.Testing.SpecFlow": "1.6.0", - "Microsoft.NET.Test.Sdk": "16.11.0", - "Moq": "4.18.2", + "Corvus.Testing.SpecFlow": "2.0.0", + "Microsoft.NET.Test.Sdk": "17.4.0", + "Moq": "4.18.3", "SpecFlow.NUnit.Runners": "3.9.74", - "coverlet.msbuild": "3.1.2" + "coverlet.msbuild": "3.2.0" } }, "Endjin.RecommendedPractices.GitHub": { @@ -105,12 +105,12 @@ }, "Corvus.Testing.SpecFlow": { "type": "Transitive", - "resolved": "1.6.0", - "contentHash": "0Qg8suJQ6TbvQK1ND51LK6kP0x1lZfqQVpbLf0CqKcIVLrJLmsb2Az+j635D6RJNTZ6jYcWxxtFEh314bERaMQ==", + "resolved": "2.0.0", + "contentHash": "dNCAIMrssh6jG7HO8FlTRzdNIHcE/X8B6Zlsdfwi9LbI0EX2IZGiFZosp+XhFQDYZ5ttAzJjO2ZWUqhnpglWWQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.31", - "Microsoft.Extensions.DependencyInjection": "3.1.31", - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.31", + "Microsoft.Extensions.Configuration.Abstractions": "6.0.0", + "Microsoft.Extensions.DependencyInjection": "6.0.1", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "NUnit": "3.13.3", "SpecFlow": "3.9.74", "System.Management": "4.7.0" @@ -118,8 +118,8 @@ }, "coverlet.msbuild": { "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "QhM0fnDtmIMImY7oxyQ/kh1VYtRxPyRVeLwRUGuUvI6Xp83pSYG9gerK8WgJj4TzUl7ISziADUGtIWKhtlbkbQ==" + "resolved": "3.2.0", + "contentHash": "lu/eJJpqJb4qy3BGPtDD/LI5RSOwXYYyRErTyaG0OTP69llzVK3FEe74hBQx0JtLUTLEVBfERV4uGYcE1Br2sg==" }, "Endjin.RecommendedPractices": { "type": "Transitive", @@ -168,8 +168,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "wf6lpAeCqP0KFfbDVtfL50lr7jY1gq0+0oSphyksfLOEygMDXqnaxHK5LPFtMEhYSEtgXdNyXNnEddOqQQUdlQ==" + "resolved": "17.4.0", + "contentHash": "2oZbSVTC2nAvQ2DnbXLlXS+c25ZyZdWeNd+znWwAxwGaPh9dwQ5NBsYyqQB7sKmJKIUdkKGmN3rzFzjVC81Dtg==" }, "Microsoft.CSharp": { "type": "Transitive", @@ -216,8 +216,8 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + "resolved": "6.0.1", + "contentHash": "vWXPg3HJQIpZkENn1KWq8SfbqVujVD7S7vIAyFXXqK5xkf1Vho+vG0bLBCHxU36lD1cLLtmGpfYf0B3MYFi9tQ==", "dependencies": { "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0" @@ -276,11 +276,11 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "f4mbG1SUSkNWF5p7B3Y8ZxMsvKhxCmpZhdl+w6tMtLSUGE7Izi1syU6TkmKOvB2BV66pdbENConFAISOix4ohQ==", + "resolved": "17.4.0", + "contentHash": "VtNZQ83ntG2aEUjy1gq6B4HNdn96se6FmdY/03At8WiqDReGrApm6OB2fNiSHz9D6IIEtWtNZ2FSH0RJDVXl/w==", "dependencies": { - "Microsoft.CodeCoverage": "16.11.0", - "Microsoft.TestPlatform.TestHost": "16.11.0" + "Microsoft.CodeCoverage": "17.4.0", + "Microsoft.TestPlatform.TestHost": "17.4.0" } }, "Microsoft.NETCore.Platforms": { @@ -295,15 +295,15 @@ }, "Microsoft.OpenApi": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "JzSCWm7KGtZ8TCxB5V0ZHBHAe3TJ9AQ+/v28Cq9kVbMIM+erjes7f1W7j4CYD+PiHgaPK7Xss6jAYkwqVjU5NQ==" + "resolved": "1.4.5", + "contentHash": "qsKbcFvY9/yUXiLTSsXd76RePfOHTbqMYBmHlFe0j97XZy1I7g8Rj81iw2SpA2pwQO7HgwGN+Ogd/xdKRKjJTg==" }, "Microsoft.OpenApi.Readers": { "type": "Transitive", - "resolved": "1.4.4", - "contentHash": "SWVnL20hz01q/DTLpaVlZ8sHweQZJbNNff/MfQYuucDJkOoU0ucqDtURDLs1ynwg79foTx5r1x2Ab5MTWqM2vg==", + "resolved": "1.4.5", + "contentHash": "s5LG9sD1twambOgAb/QMuag93oY8H9jm0XA+8IgKolSuTbxJDRjZBRkkVUjZz1d6YEDS3HJjHVd541a5uNk8VQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.4", + "Microsoft.OpenApi": "1.4.5", "SharpYaml": "2.1.0" } }, @@ -323,20 +323,20 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "EiknJx9N9Z30gs7R+HHhki7fA8EiiM3pwD1vkw3bFsBC8kdVq/O7mHf1hrg5aJp+ASO6BoOzQueD2ysfTOy/Bg==", + "resolved": "17.4.0", + "contentHash": "oWe7A0wrZhxagTOcaxJ9r0NXTbgkiBQQuCpCXxnP06NsGV/qOoaY2oaangAJbOUrwEx0eka1do400NwNCjfytw==", "dependencies": { - "NuGet.Frameworks": "5.0.0", + "NuGet.Frameworks": "5.11.0", "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "16.11.0", - "contentHash": "/Q+R0EcCJE8JaYCk+bGReicw/xrB0HhecrYrUcLbn95BnAlaTJrZhoLkUhvtKTAVtqX/AIKWXYtutiU/Q6QUgg==", + "resolved": "17.4.0", + "contentHash": "sUx48fu9wgQF1JxzXeSVtzb7KoKpJrdtIzsFamxET3ZYOKXj+Ej13HWZ0U2nuMVZtZVHBmE+KS3Vv5cIdTlycQ==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "16.11.0", - "Newtonsoft.Json": "9.0.1" + "Microsoft.TestPlatform.ObjectModel": "17.4.0", + "Newtonsoft.Json": "13.0.1" } }, "Microsoft.Win32.Primitives": { @@ -360,8 +360,8 @@ }, "Moq": { "type": "Transitive", - "resolved": "4.18.2", - "contentHash": "SjxKYS5nX6prcaT8ZjbkONh3vnh0Rxru09+gQ1a07v4TM530Oe/jq3Q4dOZPfo1wq0LYmTgLOZKrqRfEx4auPw==", + "resolved": "4.18.3", + "contentHash": "nmV2lludVOFmVi+Vtq9twX1/SDiEVyYDURzxW39gUBqjyoXmdyNwJSeOfSCJoJTXDXBVfFNfEljB5UWGj/cKnQ==", "dependencies": { "Castle.Core": "5.1.0" } @@ -381,8 +381,8 @@ }, "NuGet.Frameworks": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "c5JVjuVAm4f7E9Vj+v09Z9s2ZsqFDjBpcsyS3M9xRo0bEdm/LVZSzLxxNvfvAwRiiE8nwe1h2G4OwiwlzFKXlA==" + "resolved": "5.11.0", + "contentHash": "eaiXkUjC4NPcquGWzAGMXjuxvLwc6XGKMptSyOGQeT0X70BUZObuybJFZLA0OfTdueLd3US23NBPTBb6iF3V1Q==" }, "NUnit": { "type": "Transitive", @@ -1523,7 +1523,7 @@ "Microsoft.CSharp": "[4.7.0, )", "Microsoft.Extensions.Configuration.Abstractions": "[6.0.0, )", "Microsoft.Extensions.Logging": "[6.0.0, )", - "Microsoft.OpenApi.Readers": "[1.4.4, )", + "Microsoft.OpenApi.Readers": "[1.4.5, )", "System.Interactive": "[4.1.*, )", "System.Text.Encodings.Web": "[4.7.*, )", "Tavis.UriTemplates": "[1.1.1, )" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d154a4715..4906a11c4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,8 +22,7 @@ jobs: service_connection_nuget_org: $(Endjin_Service_Connection_NuGet_Org) service_connection_github: $(Endjin_Service_Connection_GitHub) solution_to_build: $(Endjin_Solution_To_Build) - netSdkVersion: '5.x' + netSdkVersion: '7.x' additionalNetSdkVersions: - - '3.x' - '6.x' compileTasksServiceConnection: endjin-acr-reader \ No newline at end of file