From af568cf6749bbed5c962dfc8f9db3922f4518a26 Mon Sep 17 00:00:00 2001 From: Jonathan George Date: Fri, 22 May 2020 00:02:46 +0100 Subject: [PATCH] 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; + } + } +}