From af568cf6749bbed5c962dfc8f9db3922f4518a26 Mon Sep 17 00:00:00 2001 From: Jonathan George Date: Fri, 22 May 2020 00:02:46 +0100 Subject: [PATCH 1/2] Added some basic specs for OpenApiDocumentProvider, as well as a failing test illustrating https://github.com/menes-dotnet/Menes/issues/39 --- Solutions/Menes.Specs/Data/PetStore.yaml | 684 ++++++++++++++++++ .../Features/OpenApiDocumentProvider.feature | 39 + .../OpenApiDocumentProvider.feature.cs | 221 ++++++ Solutions/Menes.Specs/Menes.Specs.csproj | 2 + .../Steps/OpenApiDocumentProviderSteps.cs | 90 +++ 5 files changed, 1036 insertions(+) create mode 100644 Solutions/Menes.Specs/Data/PetStore.yaml create mode 100644 Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature create mode 100644 Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs create mode 100644 Solutions/Menes.Specs/Steps/OpenApiDocumentProviderSteps.cs diff --git a/Solutions/Menes.Specs/Data/PetStore.yaml b/Solutions/Menes.Specs/Data/PetStore.yaml new file mode 100644 index 000000000..167c50e6d --- /dev/null +++ b/Solutions/Menes.Specs/Data/PetStore.yaml @@ -0,0 +1,684 @@ +openapi: 3.0.0 +servers: + - url: 'https://petstore.swagger.io/v2' + - url: 'http://petstore.swagger.io/v2' +info: + description: 'This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.' + version: 1.0.0 + title: Swagger Petstore + termsOfService: 'http://swagger.io/terms/' + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: 'http://swagger.io' + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user + externalDocs: + description: Find out more about our store + url: 'http://swagger.io' +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + explode: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: 'Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.' + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 10 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + minimum: 1 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be updated + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + User: + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Category: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Category + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + Pet: + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + requestBodies: + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'https://petstore.swagger.io/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature new file mode 100644 index 000000000..8c1d6d216 --- /dev/null +++ b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature @@ -0,0 +1,39 @@ +@perScenarioContainer +Feature: OpenApiDocumentProvider + In order to route requests to OpenApi operations + As a developer + I want to load my OpenApi definition and use it to match requests to operations + +Scenario: Load an OpenApi document + Given I load an OpenApi document from the embedded resource 'Menes.Specs.Data.PetStore.yaml' and call it 'PetStore' + When I add the OpenApi document called 'PetStore' to the OpenApiDocumentProvider + Then the OpenApiDocumentProvider contains 1 document + +Scenario Outline: Match requests to operation path templates - success + Given I load an OpenApi document from the embedded resource 'Menes.Specs.Data.PetStore.yaml' and call it 'PetStore' + And I add the OpenApi document called 'PetStore' to the OpenApiDocumentProvider + When I get the operation path template for path '' and method '' + Then an operation template is returned + And the operation template has operation Id '' + + Examples: + | Description | Path | Method | Expected Operation Id | + | GET with path parameter | /pet/65 | GET | getPetById | + | POST without path parameter | /pet | POST | addPet | + | PUT without path parameter | /pet | PUT | updatePet | + | POST with path parameter | /pet/65 | POST | updatePetWithForm | + | DELETE with path parameter | /pet/65 | DELETE | deletePet | + | GET with query parameter | /pet/findByStatus | GET | findPetsByStatus | + +Scenario Outline: Match requests to operation path templates - failure + Given I load an OpenApi document from the embedded resource 'Menes.Specs.Data.PetStore.yaml' and call it 'PetStore' + And I add the OpenApi document called 'PetStore' to the OpenApiDocumentProvider + When I get the operation path template for path '' and method '' + Then no operation template is returned + + Examples: + | Description | Path | Method | + | No matching path | /dogs | GET | + | Invalid method for the specified path | /pet | GET | + | GET with path parameter value that does not match schema | /pet/fenton | GET | + | End of request path matches a specified path | /this/is/unexpected/pet/findByStatus | GET | diff --git a/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs new file mode 100644 index 000000000..23d805a7d --- /dev/null +++ b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs @@ -0,0 +1,221 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by SpecFlow (http://www.specflow.org/). +// SpecFlow Version:3.1.0.0 +// SpecFlow Generator Version:3.1.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +#region Designer generated code +#pragma warning disable +namespace Menes.Specs.Features +{ + using TechTalk.SpecFlow; + using System; + using System.Linq; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.1.0.0")] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [NUnit.Framework.TestFixtureAttribute()] + [NUnit.Framework.DescriptionAttribute("OpenApiDocumentProvider")] + [NUnit.Framework.CategoryAttribute("perScenarioContainer")] + public partial class OpenApiDocumentProviderFeature + { + + private TechTalk.SpecFlow.ITestRunner testRunner; + + private string[] _featureTags = new string[] { + "perScenarioContainer"}; + +#line 1 "OpenApiDocumentProvider.feature" +#line hidden + + [NUnit.Framework.OneTimeSetUpAttribute()] + public virtual void FeatureSetup() + { + testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner(); + TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "OpenApiDocumentProvider", "\tIn order to route requests to OpenApi operations\r\n\tAs a developer\r\n\tI want to lo" + + "ad my OpenApi definition and use it to match requests to operations", ProgrammingLanguage.CSharp, new string[] { + "perScenarioContainer"}); + testRunner.OnFeatureStart(featureInfo); + } + + [NUnit.Framework.OneTimeTearDownAttribute()] + public virtual void FeatureTearDown() + { + testRunner.OnFeatureEnd(); + testRunner = null; + } + + [NUnit.Framework.SetUpAttribute()] + public virtual void TestInitialize() + { + } + + [NUnit.Framework.TearDownAttribute()] + public virtual void TestTearDown() + { + testRunner.OnScenarioEnd(); + } + + public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) + { + testRunner.OnScenarioInitialize(scenarioInfo); + testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); + } + + public virtual void ScenarioStart() + { + testRunner.OnScenarioStart(); + } + + public virtual void ScenarioCleanup() + { + testRunner.CollectScenarioErrors(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Load an OpenApi document")] + public virtual void LoadAnOpenApiDocument() + { + string[] tagsOfScenario = ((string[])(null)); + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Load an OpenApi document", null, ((string[])(null))); +#line 7 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 8 + testRunner.Given("I load an OpenApi document from the embedded resource \'Menes.Specs.Data.PetStore." + + "yaml\' and call it \'PetStore\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line hidden +#line 9 + testRunner.When("I add the OpenApi document called \'PetStore\' to the OpenApiDocumentProvider", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 10 + testRunner.Then("the OpenApiDocumentProvider contains 1 document", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Match requests to operation path templates - success")] + [NUnit.Framework.TestCaseAttribute("GET with path parameter", "/pet/65", "GET", "getPetById", null)] + [NUnit.Framework.TestCaseAttribute("POST without path parameter", "/pet", "POST", "addPet", null)] + [NUnit.Framework.TestCaseAttribute("PUT without path parameter", "/pet", "PUT", "updatePet", null)] + [NUnit.Framework.TestCaseAttribute("POST with path parameter", "/pet/65", "POST", "updatePetWithForm", null)] + [NUnit.Framework.TestCaseAttribute("DELETE with path parameter", "/pet/65", "DELETE", "deletePet", null)] + [NUnit.Framework.TestCaseAttribute("GET with query parameter", "/pet/findByStatus", "GET", "findPetsByStatus", null)] + public virtual void MatchRequestsToOperationPathTemplates_Success(string description, string path, string method, string expectedOperationId, string[] exampleTags) + { + string[] tagsOfScenario = exampleTags; + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Match requests to operation path templates - success", null, exampleTags); +#line 12 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 13 + testRunner.Given("I load an OpenApi document from the embedded resource \'Menes.Specs.Data.PetStore." + + "yaml\' and call it \'PetStore\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line hidden +#line 14 + testRunner.And("I add the OpenApi document called \'PetStore\' to the OpenApiDocumentProvider", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden +#line 15 + testRunner.When(string.Format("I get the operation path template for path \'{0}\' and method \'{1}\'", path, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 16 + testRunner.Then("an operation template is returned", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden +#line 17 + testRunner.And(string.Format("the operation template has operation Id \'{0}\'", expectedOperationId), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden + } + this.ScenarioCleanup(); + } + + [NUnit.Framework.TestAttribute()] + [NUnit.Framework.DescriptionAttribute("Match requests to operation path templates - failure")] + [NUnit.Framework.TestCaseAttribute("No matching path", "/dogs", "GET", null)] + [NUnit.Framework.TestCaseAttribute("Invalid method for the specified path", "/pet", "GET", null)] + [NUnit.Framework.TestCaseAttribute("GET with path parameter value that does not match schema", "/pet/fenton", "GET", null)] + [NUnit.Framework.TestCaseAttribute("End of request path matches a specified path", "/this/is/unexpected/pet/findByStatus", "GET", null)] + public virtual void MatchRequestsToOperationPathTemplates_Failure(string description, string path, string method, string[] exampleTags) + { + string[] tagsOfScenario = exampleTags; + TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Match requests to operation path templates - failure", null, exampleTags); +#line 28 +this.ScenarioInitialize(scenarioInfo); +#line hidden + bool isScenarioIgnored = default(bool); + bool isFeatureIgnored = default(bool); + if ((tagsOfScenario != null)) + { + isScenarioIgnored = tagsOfScenario.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((this._featureTags != null)) + { + isFeatureIgnored = this._featureTags.Where(__entry => __entry != null).Where(__entry => String.Equals(__entry, "ignore", StringComparison.CurrentCultureIgnoreCase)).Any(); + } + if ((isScenarioIgnored || isFeatureIgnored)) + { + testRunner.SkipScenario(); + } + else + { + this.ScenarioStart(); +#line 29 + testRunner.Given("I load an OpenApi document from the embedded resource \'Menes.Specs.Data.PetStore." + + "yaml\' and call it \'PetStore\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); +#line hidden +#line 30 + testRunner.And("I add the OpenApi document called \'PetStore\' to the OpenApiDocumentProvider", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); +#line hidden +#line 31 + testRunner.When(string.Format("I get the operation path template for path \'{0}\' and method \'{1}\'", path, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); +#line hidden +#line 32 + testRunner.Then("no operation template is returned", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); +#line hidden + } + this.ScenarioCleanup(); + } + } +} +#pragma warning restore +#endregion diff --git a/Solutions/Menes.Specs/Menes.Specs.csproj b/Solutions/Menes.Specs/Menes.Specs.csproj index 22fd7ef56..7d779eafc 100644 --- a/Solutions/Menes.Specs/Menes.Specs.csproj +++ b/Solutions/Menes.Specs/Menes.Specs.csproj @@ -33,10 +33,12 @@ + + Always diff --git a/Solutions/Menes.Specs/Steps/OpenApiDocumentProviderSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiDocumentProviderSteps.cs new file mode 100644 index 000000000..9296830cf --- /dev/null +++ b/Solutions/Menes.Specs/Steps/OpenApiDocumentProviderSteps.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) Endjin. All rights reserved. +// + +namespace Menes.Specs.Steps +{ + using Corvus.SpecFlow.Extensions; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.OpenApi.Models; + using NUnit.Framework; + using NUnit.Framework.Internal; + using TechTalk.SpecFlow; + + [Binding] + public class OpenApiDocumentProviderSteps + { + private readonly ScenarioContext scenarioContext; + + public OpenApiDocumentProviderSteps(ScenarioContext scenarioContext) + { + this.scenarioContext = scenarioContext; + } + + [Given("I load an OpenApi document from the embedded resource '(.*)' and call it '(.*)'")] + public void GivenILoadAnOpenApiDocumentFromTheEmbeddedResourceAndCallIt( + string embeddedResourceName, + string documentName) + { + OpenApiDocument openApiDocument = OpenApiServiceDefinitions.GetOpenApiServiceFromEmbeddedDefinition( + typeof(OpenApiDocumentProviderSteps).Assembly, + embeddedResourceName); + + this.scenarioContext.Set(openApiDocument, documentName); + } + + [Given("I add the OpenApi document called '(.*)' to the OpenApiDocumentProvider")] + [When("I add the OpenApi document called '(.*)' to the OpenApiDocumentProvider")] + public void WhenIAddTheOpenApiDocumentCalledToTheOpenApiDocumentProvider(string documentName) + { + OpenApiDocumentProvider documentProvider = this.GetOpenApiDocumentProvider(); + OpenApiDocument document = this.scenarioContext.Get(documentName); + + documentProvider.Add(document); + } + + [When("I get the operation path template for path '(.*)' and method '(.*)'")] + public void WhenIGetTheOperationPathTemplateForPathAndMethod(string path, string method) + { + OpenApiDocumentProvider documentProvider = this.GetOpenApiDocumentProvider(); + documentProvider.GetOperationPathTemplate(path, method, out OpenApiOperationPathTemplate? template); + this.scenarioContext.Set(template); + } + + [Then("the OpenApiDocumentProvider contains (.*) document")] + public void ThenTheOpenApiDocumentProviderContainsDocument(int expectedDocumentCount) + { + OpenApiDocumentProvider documentProvider = this.GetOpenApiDocumentProvider(); + Assert.AreEqual(expectedDocumentCount, documentProvider.AddedOpenApiDocuments.Count); + } + + [Then("an operation template is returned")] + public void ThenAnOperationTemplateIsReturned() + { + OpenApiOperationPathTemplate? template = this.scenarioContext.Get(); + Assert.IsNotNull(template); + } + + [Then("no operation template is returned")] + public void ThenNoOperationTemplateIsReturned() + { + OpenApiOperationPathTemplate? template = this.scenarioContext.Get(); + Assert.IsNull(template); + } + + [Then("the operation template has operation Id '(.*)'")] + public void ThenTheOperationTemplateHasOperationId(string expectedOperationId) + { + OpenApiOperationPathTemplate? template = this.scenarioContext.Get(); + Assert.AreEqual(expectedOperationId, template?.Operation.OperationId); + } + + private OpenApiDocumentProvider GetOpenApiDocumentProvider() + { + IOpenApiDocumentProvider documentProvider = ContainerBindings.GetServiceProvider(this.scenarioContext) + .GetRequiredService(); + + return (OpenApiDocumentProvider)documentProvider; + } + } +} From a959699816a6307f91d39b9d1b77f5bfd4be65ef Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Fri, 22 May 2020 12:57:17 +0100 Subject: [PATCH 2/2] Added support for the Server override in the root and "Path", and handle that in the operation resolution. --- .../Menes/Internal/OpenApiDocumentProvider.cs | 66 +++++++++++++++---- .../Menes/OpenApiOperationPathTemplate.cs | 23 ++++++- .../Menes/OpenApiPathTemplate.cs | 11 +++- .../AspNetCore/HttpRequestExtensions.cs | 3 +- .../Features/OpenApiDocumentProvider.feature | 36 ++++++---- .../OpenApiDocumentProvider.feature.cs | 38 ++++++----- .../Steps/CommonInstrumentationSteps.cs | 3 +- .../Steps/OpenApiOperationInvokerSteps.cs | 2 +- 8 files changed, 134 insertions(+), 48 deletions(-) diff --git a/Solutions/Menes.Abstractions/Menes/Internal/OpenApiDocumentProvider.cs b/Solutions/Menes.Abstractions/Menes/Internal/OpenApiDocumentProvider.cs index fa82e2e7d..80ac2740b 100644 --- a/Solutions/Menes.Abstractions/Menes/Internal/OpenApiDocumentProvider.cs +++ b/Solutions/Menes.Abstractions/Menes/Internal/OpenApiDocumentProvider.cs @@ -5,15 +5,20 @@ namespace Menes { using System; + using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Linq; + using System.Reflection.Metadata; using System.Text; using System.Text.Encodings.Web; + using System.Text.RegularExpressions; using Corvus.Extensions; using Menes.Exceptions; using Menes.Internal; using Menes.Validation; + using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using Tavis.UriTemplates; @@ -41,6 +46,8 @@ namespace Menes /// public class OpenApiDocumentProvider : IOpenApiDocumentProvider { + private static readonly ConcurrentDictionary RegexCache = new ConcurrentDictionary(); + private readonly IList pathTemplates = new List(); private readonly ILogger logger; private readonly List addedOpenApiDocuments; @@ -137,7 +144,7 @@ public void Add(OpenApiDocument document) } this.addedOpenApiDocuments.Add(document); - this.pathTemplates.AddRange(document.Paths.Select(path => new OpenApiPathTemplate(path.Key, path.Value))); + this.pathTemplates.AddRange(document.Paths.Select(path => new OpenApiPathTemplate(path.Key, path.Value, document))); this.pathTemplatesByOperationId = null; if (this.logger.IsEnabled(LogLevel.Trace)) { @@ -150,20 +157,27 @@ public bool GetOperationPathTemplate(string requestPath, string method, [NotNull { foreach (OpenApiPathTemplate template in this.pathTemplates) { - if (template.Match.IsMatch(requestPath)) + Match match = template.Match.Match(requestPath); + + if (match.Success) { - if (template.PathItem.Operations.TryGetValue(method.ToOperationType(), out OpenApiOperation operation)) + OpenApiServer? server = MatchServer(match, requestPath, template); + + if (server != null) { - this.logger.LogInformation( - "Matched request [{method}] [{requestPath}] with template [{template}] to [{operation}]", - method, - requestPath, - template.UriTemplate, - operation.GetOperationId()); - - // This is the success path - operationPathTemplate = new OpenApiOperationPathTemplate(operation, template); - return true; + if (template.PathItem.Operations.TryGetValue(method.ToOperationType(), out OpenApiOperation operation)) + { + this.logger.LogInformation( + "Matched request [{method}] [{requestPath}] with template [{template}] to [{operation}]", + method, + requestPath, + template.UriTemplate, + operation.GetOperationId()); + + // This is the success path + operationPathTemplate = new OpenApiOperationPathTemplate(operation, template, server); + return true; + } } } } @@ -176,5 +190,31 @@ public bool GetOperationPathTemplate(string requestPath, string method, [NotNull operationPathTemplate = null; return false; } + + private static OpenApiServer? MatchServer(Match match, string requestPath, OpenApiPathTemplate template) + { + string precursor = match.Index == 0 ? string.Empty : requestPath.Substring(0, match.Index); + if (template.PathItem.Servers.Count > 0) + { + return MatchPrecursor(precursor, template.PathItem.Servers); + } + + if (template.Document?.Servers.Count > 0) + { + return MatchPrecursor(precursor, template.Document.Servers); + } + + return null; + } + + private static OpenApiServer? MatchPrecursor(string precursor, IList servers) + { + return servers.FirstOrDefault(s => + { + Regex regex = RegexCache.GetOrAdd(s, new Regex(UriTemplate.CreateMatchingRegex2(s.Url), RegexOptions.Compiled)); + Match match = regex.Match(precursor); + return match.Success && match.Index == 0; + }); + } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/OpenApiOperationPathTemplate.cs b/Solutions/Menes.Abstractions/Menes/OpenApiOperationPathTemplate.cs index 45bbb8c49..dc9e31ecb 100644 --- a/Solutions/Menes.Abstractions/Menes/OpenApiOperationPathTemplate.cs +++ b/Solutions/Menes.Abstractions/Menes/OpenApiOperationPathTemplate.cs @@ -9,6 +9,7 @@ namespace Menes using System.Linq; using Corvus.Extensions; using Microsoft.OpenApi.Models; + using Tavis.UriTemplates; /// /// A URI match for an operation. @@ -20,10 +21,12 @@ public class OpenApiOperationPathTemplate /// /// The matching OpenAPI operation. /// The matching OpenAPI path template. - public OpenApiOperationPathTemplate(OpenApiOperation operation, OpenApiPathTemplate openApiPathTemplate) + /// The server for this path template. + public OpenApiOperationPathTemplate(OpenApiOperation operation, OpenApiPathTemplate openApiPathTemplate, OpenApiServer? server) { this.Operation = operation; this.OpenApiPathTemplate = openApiPathTemplate; + this.Server = server; } /// @@ -36,6 +39,11 @@ public OpenApiOperationPathTemplate(OpenApiOperation operation, OpenApiPathTempl /// public OpenApiPathTemplate OpenApiPathTemplate { get; } + /// + /// Gets the server associated with this path template. + /// + public OpenApiServer? Server { get; } + /// /// Builds the list of Open API parameters for this match. /// @@ -58,7 +66,18 @@ public IList BuildOpenApiParameters() /// A dictionary of parameter names to values. public IDictionary BuildTemplateParameterValues(Uri requestUri) { - return this.OpenApiPathTemplate.UriTemplate.GetParameters(requestUri); + IDictionary pathParameters = this.OpenApiPathTemplate.UriTemplate.GetParameters(requestUri); + + if (this.Server != null) + { + IDictionary serverParameters = new UriTemplate(this.Server.Url).GetParameters(requestUri); + if (serverParameters != null) + { + pathParameters = pathParameters.Merge(serverParameters); + } + } + + return pathParameters; } } } \ No newline at end of file diff --git a/Solutions/Menes.Abstractions/Menes/OpenApiPathTemplate.cs b/Solutions/Menes.Abstractions/Menes/OpenApiPathTemplate.cs index 34f1b031c..c3966cf75 100644 --- a/Solutions/Menes.Abstractions/Menes/OpenApiPathTemplate.cs +++ b/Solutions/Menes.Abstractions/Menes/OpenApiPathTemplate.cs @@ -18,11 +18,13 @@ public class OpenApiPathTemplate /// /// The uri template. /// The path item. - public OpenApiPathTemplate(string uriTemplate, OpenApiPathItem item) + /// The Open API document in which this is a path template. + public OpenApiPathTemplate(string uriTemplate, OpenApiPathItem item, OpenApiDocument? document) { this.UriTemplate = new UriTemplate(uriTemplate); this.PathItem = item; - this.Match = new Regex(UriTemplate.CreateMatchingRegex(uriTemplate), RegexOptions.Compiled); + this.Match = new Regex(UriTemplate.CreateMatchingRegex2(uriTemplate), RegexOptions.Compiled); + this.Document = document; } /// @@ -39,5 +41,10 @@ public OpenApiPathTemplate(string uriTemplate, OpenApiPathItem item) /// Gets the which provides a match. /// public Regex Match { get; } + + /// + /// Gets the containing this path template. + /// + public OpenApiDocument? Document { get; } } } \ No newline at end of file diff --git a/Solutions/Menes.Hosting.AspNetCore/Menes/Hosting/AspNetCore/HttpRequestExtensions.cs b/Solutions/Menes.Hosting.AspNetCore/Menes/Hosting/AspNetCore/HttpRequestExtensions.cs index 6fc706cb7..bc6604857 100644 --- a/Solutions/Menes.Hosting.AspNetCore/Menes/Hosting/AspNetCore/HttpRequestExtensions.cs +++ b/Solutions/Menes.Hosting.AspNetCore/Menes/Hosting/AspNetCore/HttpRequestExtensions.cs @@ -7,6 +7,7 @@ namespace Menes.Hosting.AspNetCore using System.Threading.Tasks; using Menes; using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; /// @@ -23,7 +24,7 @@ public static class HttpRequestExtensions /// The result of the request. public static Task HandleRequestAsync(this IOpenApiHost host, HttpRequest httpRequest, object parameters) { - return host.HandleRequestAsync(httpRequest.Path, httpRequest.Method, httpRequest, parameters); + return host.HandleRequestAsync(httpRequest.GetDisplayUrl(), httpRequest.Method, httpRequest, parameters); } } } diff --git a/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature index 8c1d6d216..3685260cb 100644 --- a/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature +++ b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature @@ -14,16 +14,26 @@ Scenario Outline: Match requests to operation path templates - success And I add the OpenApi document called 'PetStore' to the OpenApiDocumentProvider When I get the operation path template for path '' and method '' Then an operation template is returned - And the operation template has operation Id '' + And the operation template has operation Id '' Examples: - | Description | Path | Method | Expected Operation Id | - | GET with path parameter | /pet/65 | GET | getPetById | - | POST without path parameter | /pet | POST | addPet | - | PUT without path parameter | /pet | PUT | updatePet | - | POST with path parameter | /pet/65 | POST | updatePetWithForm | - | DELETE with path parameter | /pet/65 | DELETE | deletePet | - | GET with query parameter | /pet/findByStatus | GET | findPetsByStatus | + | Description | Path | Method | Expected Operation Id | + | GET with path parameter | https://petstore.swagger.io/v2/pet/65 | GET | getPetById | + | POST without path parameter | https://petstore.swagger.io/v2/pet | POST | addPet | + | PUT without path parameter | https://petstore.swagger.io/v2/pet | PUT | updatePet | + | POST with path parameter | https://petstore.swagger.io/v2/pet/65 | POST | updatePetWithForm | + | DELETE with path parameter | https://petstore.swagger.io/v2/pet/65 | DELETE | deletePet | + | GET with query parameter | https://petstore.swagger.io/v2/pet/findByStatus | GET | findPetsByStatus | + # We expect this to succeed because validation is not performed until later in the pipeline + | GET with path parameter value that does not match schema | https://petstore.swagger.io/v2/pet/fenton | GET | getPetById | + | GET with path parameter second server | https://petstore.swagger.io/v2/pet/65 | GET | getPetById | + | POST without path parameter second server | https://petstore.swagger.io/v2/pet | POST | addPet | + | PUT without path parameter second server | https://petstore.swagger.io/v2/pet | PUT | updatePet | + | POST with path parameter second server | https://petstore.swagger.io/v2/pet/65 | POST | updatePetWithForm | + | DELETE with path parameter second server | https://petstore.swagger.io/v2/pet/65 | DELETE | deletePet | + | GET with query parameter second server | https://petstore.swagger.io/v2/pet/findByStatus | GET | findPetsByStatus | + # We expect this to succeed because validation is not performed until later in the pipeline + | GET with path parameter value that does not match schema second server | https://petstore.swagger.io/v2/pet/fenton | GET | getPetById | Scenario Outline: Match requests to operation path templates - failure Given I load an OpenApi document from the embedded resource 'Menes.Specs.Data.PetStore.yaml' and call it 'PetStore' @@ -32,8 +42,8 @@ Scenario Outline: Match requests to operation path templates - failure Then no operation template is returned Examples: - | Description | Path | Method | - | No matching path | /dogs | GET | - | Invalid method for the specified path | /pet | GET | - | GET with path parameter value that does not match schema | /pet/fenton | GET | - | End of request path matches a specified path | /this/is/unexpected/pet/findByStatus | GET | + | Description | Path | Method | + | No matching path | https://petstore.swagger.io/v2/dogs | GET | + | Invalid method for the specified path | https://petstore.swagger.io/v2/pet | GET | + | End of request path matches a specified path | https://petstore.swagger.io/v2/this/is/unexpected/pet/findByStatus | GET | + | No matching path | https://duff.server.io/v2/pet/65 | GET | \ No newline at end of file diff --git a/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs index 23d805a7d..8110b76a2 100644 --- a/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs +++ b/Solutions/Menes.Specs/Features/OpenApiDocumentProvider.feature.cs @@ -119,12 +119,20 @@ public virtual void LoadAnOpenApiDocument() [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Match requests to operation path templates - success")] - [NUnit.Framework.TestCaseAttribute("GET with path parameter", "/pet/65", "GET", "getPetById", null)] - [NUnit.Framework.TestCaseAttribute("POST without path parameter", "/pet", "POST", "addPet", null)] - [NUnit.Framework.TestCaseAttribute("PUT without path parameter", "/pet", "PUT", "updatePet", null)] - [NUnit.Framework.TestCaseAttribute("POST with path parameter", "/pet/65", "POST", "updatePetWithForm", null)] - [NUnit.Framework.TestCaseAttribute("DELETE with path parameter", "/pet/65", "DELETE", "deletePet", null)] - [NUnit.Framework.TestCaseAttribute("GET with query parameter", "/pet/findByStatus", "GET", "findPetsByStatus", null)] + [NUnit.Framework.TestCaseAttribute("GET with path parameter", "https://petstore.swagger.io/v2/pet/65", "GET", "getPetById", null)] + [NUnit.Framework.TestCaseAttribute("POST without path parameter", "https://petstore.swagger.io/v2/pet", "POST", "addPet", null)] + [NUnit.Framework.TestCaseAttribute("PUT without path parameter", "https://petstore.swagger.io/v2/pet", "PUT", "updatePet", null)] + [NUnit.Framework.TestCaseAttribute("POST with path parameter", "https://petstore.swagger.io/v2/pet/65", "POST", "updatePetWithForm", null)] + [NUnit.Framework.TestCaseAttribute("DELETE with path parameter", "https://petstore.swagger.io/v2/pet/65", "DELETE", "deletePet", null)] + [NUnit.Framework.TestCaseAttribute("GET with query parameter", "https://petstore.swagger.io/v2/pet/findByStatus", "GET", "findPetsByStatus", null)] + [NUnit.Framework.TestCaseAttribute("GET with path parameter value that does not match schema", "https://petstore.swagger.io/v2/pet/fenton", "GET", "getPetById", null)] + [NUnit.Framework.TestCaseAttribute("GET with path parameter second server", "https://petstore.swagger.io/v2/pet/65", "GET", "getPetById", null)] + [NUnit.Framework.TestCaseAttribute("POST without path parameter second server", "https://petstore.swagger.io/v2/pet", "POST", "addPet", null)] + [NUnit.Framework.TestCaseAttribute("PUT without path parameter second server", "https://petstore.swagger.io/v2/pet", "PUT", "updatePet", null)] + [NUnit.Framework.TestCaseAttribute("POST with path parameter second server", "https://petstore.swagger.io/v2/pet/65", "POST", "updatePetWithForm", null)] + [NUnit.Framework.TestCaseAttribute("DELETE with path parameter second server", "https://petstore.swagger.io/v2/pet/65", "DELETE", "deletePet", null)] + [NUnit.Framework.TestCaseAttribute("GET with query parameter second server", "https://petstore.swagger.io/v2/pet/findByStatus", "GET", "findPetsByStatus", null)] + [NUnit.Framework.TestCaseAttribute("GET with path parameter value that does not match schema second server", "https://petstore.swagger.io/v2/pet/fenton", "GET", "getPetById", null)] public virtual void MatchRequestsToOperationPathTemplates_Success(string description, string path, string method, string expectedOperationId, string[] exampleTags) { string[] tagsOfScenario = exampleTags; @@ -171,15 +179,15 @@ public virtual void MatchRequestsToOperationPathTemplates_Success(string descrip [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Match requests to operation path templates - failure")] - [NUnit.Framework.TestCaseAttribute("No matching path", "/dogs", "GET", null)] - [NUnit.Framework.TestCaseAttribute("Invalid method for the specified path", "/pet", "GET", null)] - [NUnit.Framework.TestCaseAttribute("GET with path parameter value that does not match schema", "/pet/fenton", "GET", null)] - [NUnit.Framework.TestCaseAttribute("End of request path matches a specified path", "/this/is/unexpected/pet/findByStatus", "GET", null)] + [NUnit.Framework.TestCaseAttribute("No matching path", "https://petstore.swagger.io/v2/dogs", "GET", null)] + [NUnit.Framework.TestCaseAttribute("Invalid method for the specified path", "https://petstore.swagger.io/v2/pet", "GET", null)] + [NUnit.Framework.TestCaseAttribute("End of request path matches a specified path", "https://petstore.swagger.io/v2/this/is/unexpected/pet/findByStatus", "GET", null)] + [NUnit.Framework.TestCaseAttribute("No matching path", "https://duff.server.io/v2/pet/65", "GET", null)] public virtual void MatchRequestsToOperationPathTemplates_Failure(string description, string path, string method, string[] exampleTags) { string[] tagsOfScenario = exampleTags; TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Match requests to operation path templates - failure", null, exampleTags); -#line 28 +#line 38 this.ScenarioInitialize(scenarioInfo); #line hidden bool isScenarioIgnored = default(bool); @@ -199,17 +207,17 @@ public virtual void MatchRequestsToOperationPathTemplates_Failure(string descrip else { this.ScenarioStart(); -#line 29 +#line 39 testRunner.Given("I load an OpenApi document from the embedded resource \'Menes.Specs.Data.PetStore." + "yaml\' and call it \'PetStore\'", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line hidden -#line 30 +#line 40 testRunner.And("I add the OpenApi document called \'PetStore\' to the OpenApiDocumentProvider", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden -#line 31 +#line 41 testRunner.When(string.Format("I get the operation path template for path \'{0}\' and method \'{1}\'", path, method), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line hidden -#line 32 +#line 42 testRunner.Then("no operation template is returned", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden } diff --git a/Solutions/Menes.Specs/Steps/CommonInstrumentationSteps.cs b/Solutions/Menes.Specs/Steps/CommonInstrumentationSteps.cs index 4a7c050a8..82bfc015e 100644 --- a/Solutions/Menes.Specs/Steps/CommonInstrumentationSteps.cs +++ b/Solutions/Menes.Specs/Steps/CommonInstrumentationSteps.cs @@ -25,7 +25,8 @@ public void WhenIHandleAToWithAnOperationIdOf(string method, string path, string { var template = new OpenApiOperationPathTemplate( new OpenApiOperation { OperationId = operationId }, - new OpenApiPathTemplate(path, new OpenApiPathItem())); + new OpenApiPathTemplate(path, new OpenApiPathItem(), null), + null); this.InvokerContext.OperationInvocationTask = this.Invoker.InvokeAsync(method, path, new object(), template, new Mock().Object); } diff --git a/Solutions/Menes.Specs/Steps/OpenApiOperationInvokerSteps.cs b/Solutions/Menes.Specs/Steps/OpenApiOperationInvokerSteps.cs index b7ddf6b2e..7bf9d6e2d 100644 --- a/Solutions/Menes.Specs/Steps/OpenApiOperationInvokerSteps.cs +++ b/Solutions/Menes.Specs/Steps/OpenApiOperationInvokerSteps.cs @@ -56,7 +56,7 @@ public void GivenIAmHaveConfiguredTheUnauthenticatedStatusCodeToBe(string status public void GivenTheOperationPathTemplateHasAnOperationWithAnOperationIdOf(string operationId) { this.openApiOperation = new OpenApiOperation { OperationId = operationId }; - this.operationPathTemplate = new OpenApiOperationPathTemplate(this.openApiOperation, new OpenApiPathTemplate("/", new OpenApiPathItem())); + this.operationPathTemplate = new OpenApiOperationPathTemplate(this.openApiOperation, new OpenApiPathTemplate("/", new OpenApiPathItem(), null), null); MethodInfo serviceMethod = typeof(OpenApiOperationInvokerSteps).GetMethod( nameof(this.ServiceMethodImplementation),