public void CreatePathItemForOperationReturnsCorrectPathItem(string operationName, string entitySet, OperationType operationType) { // Arrange IEdmModel model = EdmModelHelper.TripServiceModel; ODataContext context = new ODataContext(model); IEdmNavigationSource navigationSource = model.EntityContainer.FindEntitySet(entitySet); Assert.NotNull(navigationSource); // guard IEdmOperation edmOperation = model.SchemaElements.OfType <IEdmOperation>() .FirstOrDefault(o => o.Name == operationName); Assert.NotNull(edmOperation); // guard string expectSummary = "Invoke " + (edmOperation.IsAction() ? "action " : "function ") + operationName; ODataPath path = new ODataPath(new ODataNavigationSourceSegment(navigationSource), new ODataOperationSegment(edmOperation)); // Act OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); // Assert Assert.NotNull(pathItem); Assert.NotNull(pathItem.Operations); var operationKeyValue = Assert.Single(pathItem.Operations); Assert.Equal(operationType, operationKeyValue.Key); Assert.NotNull(operationKeyValue.Value); Assert.Equal(expectSummary, operationKeyValue.Value.Summary); }
private OpenApiPathItem CreatePathItem(PropertyInfo propertyInfo) { var operation = new OpenApiOperation { Tags = new List <OpenApiTag> { new OpenApiTag { Name = propertyInfo.Name } }, OperationId = propertyInfo.Name, Parameters = this.GetLocalCallMetadataHeaderParams(), Responses = new OpenApiResponses { { "200", new OpenApiResponse { Description = "Success" } } } }; var pathItem = new OpenApiPathItem { Operations = new Dictionary <OperationType, OpenApiOperation> { { OperationType.Get, operation } } }; return(pathItem); }
protected override IndexerEndpoint BuildEndpoint(OpenApiPathItem item, IEndpoint elementEndpoint) { if (!(elementEndpoint is ElementEndpoint element)) { return(null); } var operation = item.Operations[OperationType.Get]; var schema = operation.Get200Response()?.GetJsonSchema(); if (schema?.Type != "array" || schema.Items?.Reference?.Id != element?.Schema?.Reference?.Id) { return(null); } element.Schema = null; if (element.Children.Count == 0) { element = null; } return(new CollectionEndpoint { Schema = schema.Items, Element = element, Description = operation.Description ?? operation.Summary }); }
/// <summary> /// Fetches the URL value and creates multiple operations based on optional parameters. /// </summary> /// <param name="paths">The paths to be updated.</param> /// <param name="element">The xml element representing an operation in the annotation xml.</param> /// <param name="settings">The operation filter settings.</param> /// <returns>The list of generation errors, if any produced when processing the filter.</returns> public IList <GenerationError> Apply( OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings) { var generationErrors = new List <GenerationError>(); try { var paramElements = element.Elements() .Where( p => p.Name == KnownXmlStrings.Param) .ToList(); // We need both the full URL and the absolute paths for processing. // Full URL contains all path and query parameters. // Absolute path is needed to get OperationId parsed out correctly. var fullUrl = element.Elements() .FirstOrDefault(p => p.Name == KnownXmlStrings.Url) ?.Value; var absolutePath = fullUrl.UrlStringToAbsolutePath(); var operationMethod = (OperationType)Enum.Parse( typeof(OperationType), element.Elements().FirstOrDefault(p => p.Name == KnownXmlStrings.Verb)?.Value, ignoreCase: true); var allGeneratedPathStrings = GeneratePossiblePaths( absolutePath, paramElements.Where( p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path) .ToList()); foreach (var pathString in allGeneratedPathStrings) { if (!paths.ContainsKey(pathString)) { paths[pathString] = new OpenApiPathItem(); } paths[pathString].Operations[operationMethod] = new OpenApiOperation { OperationId = OperationHandler.GetOperationId(pathString, operationMethod) }; } } catch (Exception ex) { generationErrors.Add( new GenerationError { Message = ex.Message, ExceptionType = ex.GetType().Name }); } return(generationErrors); }
public void Apply(OpenApiDocument openApiDocument, DocumentFilterContext context) { var pathItem = new OpenApiPathItem(); var operation = new OpenApiOperation(); operation.Tags.Add(new OpenApiTag { Name = "GraphQL" }); operation.RequestBody = new OpenApiRequestBody() { Content = new Dictionary <string, OpenApiMediaType> { { "application/json", new OpenApiMediaType() { Schema = context.SchemaGenerator .GenerateSchema(typeof(Project), context.SchemaRepository), Example = new OpenApiString(query) } } } }; pathItem.AddOperation(OperationType.Post, operation); openApiDocument?.Paths.Add(graphEndpoint, pathItem); }
public void CreatePathItemForOperationImportReturnsCorrectPathItem(string operationImport, OperationType operationType) { // Arrange IEdmModel model = EdmModelHelper.TripServiceModel; ODataContext context = new ODataContext(model); IEdmOperationImport edmOperationImport = model.EntityContainer .OperationImports().FirstOrDefault(o => o.Name == operationImport); Assert.NotNull(edmOperationImport); // guard string expectSummary = "Invoke " + (edmOperationImport.IsActionImport() ? "actionImport " : "functionImport ") + operationImport; ODataPath path = new ODataPath(new ODataOperationImportSegment(edmOperationImport)); // Act OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); // Assert Assert.NotNull(pathItem); Assert.NotNull(pathItem.Operations); var operationKeyValue = Assert.Single(pathItem.Operations); Assert.Equal(operationType, operationKeyValue.Key); Assert.NotNull(operationKeyValue.Value); Assert.Equal(expectSummary, operationKeyValue.Value.Summary); }
/// <summary> /// Add one operation into path item. /// </summary> /// <param name="item">The path item.</param> /// <param name="operationType">The operation type.</param> protected virtual void AddOperation(OpenApiPathItem item, OperationType operationType) { IOperationHandlerProvider provider = Context.OperationHanderProvider; IOperationHandler operationHander = provider.GetHandler(Path.Kind, operationType); item.AddOperation(operationType, operationHander.CreateOperation(Context, Path)); }
public void CreateOperationImportPathItemAddsCustomAttributeValuesToPathExtensions(string operationImport) { // Arrange IEdmModel model = EdmModelHelper.TripServiceModel; ODataContext context = new(model); context.Settings.CustomXMLAttributesMapping.Add("ags:IsHidden", "x-ms-isHidden"); IEdmOperationImport edmOperationImport = model.EntityContainer .OperationImports().FirstOrDefault(o => o.Name == operationImport); Assert.NotNull(edmOperationImport); // guard ODataPath path = new(new ODataOperationImportSegment(edmOperationImport)); // Act OpenApiPathItem pathItem = _pathItemHandler.CreatePathItem(context, path); // Assert Assert.NotNull(pathItem); Assert.NotNull(pathItem.Extensions); pathItem.Extensions.TryGetValue("x-ms-isHidden", out IOpenApiExtension isHiddenExtension); string isHiddenValue = (isHiddenExtension as OpenApiString)?.Value; Assert.Equal("true", isHiddenValue); }
public ChangedPathBO Diff(OpenApiPathItem left, OpenApiPathItem right, DiffContextBO context) { var oldOperationMap = left.Operations; var newOperationMap = right.Operations; var operationsDiff = MapKeyDiff <OperationType, OpenApiOperation> .Diff(oldOperationMap, newOperationMap); var sharedMethods = operationsDiff.SharedKey; var changedPath = new ChangedPathBO(context.URL, left, right, context) { Increased = operationsDiff.Increased, Missing = operationsDiff.Missing }; foreach (var operationType in sharedMethods) { var oldOperation = oldOperationMap[operationType]; var newOperation = newOperationMap[operationType]; var diff = _openApiDiff .OperationDiff .Diff(oldOperation, newOperation, context.CopyWithMethod(operationType)); if (diff != null) { changedPath.Changed.Add(diff); } } changedPath.Extensions = _openApiDiff .ExtensionsDiff .Diff(left.Extensions, right.Extensions, context); return(ChangedUtils.IsChanged(changedPath)); }
private void ProcessEndPoints(HttpContext context, OpenApiDocument document, IReadOnlyList <IEndPointMethodHandler> endPointMethodHandlersList) { document.Paths = new OpenApiPaths(); var groupedMethods = GroupMethodsByTemplate(endPointMethodHandlersList); foreach (var groupedMethod in groupedMethods) { var pathItem = new OpenApiPathItem { Operations = new Dictionary <OperationType, OpenApiOperation>() }; foreach (var endPointHandler in groupedMethod.Value) { var apiOperation = GenerateApiOperation(document, endPointHandler.Value); var method = GetOperationTypeFromHttpMethod(endPointHandler.Value.HttpMethod); var filterContext = new OperationFilterContext(context, endPointHandler.Value.Configuration, apiOperation, document); foreach (var operationFilter in _documentationOptions.OperationFilters) { operationFilter.Apply(filterContext); } pathItem.Operations.Add(method, apiOperation); } document.Paths.Add(groupedMethod.Key, pathItem); } }
public void TrimsPlaceholders() { var itemA = new OpenApiPathItem { Summary = "Item A" }; var itemB = new OpenApiPathItem { Summary = "Item B" }; var paths = new OpenApiPaths { ["/{name}/a"] = itemA, ["/{id}/b"] = itemB, }; var tree = PathTree.From(paths); tree.Should().BeEquivalentTo(new PathTree { Children = { ["{}"] = new PathTree { Children = { ["a"] = new PathTree { Item = itemA }, ["b"] = new PathTree { Item = itemB } } } } }); }
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var logoutItem = new OpenApiPathItem(); logoutItem.AddOperation(OperationType.Post, new OpenApiOperation { Tags = new List <OpenApiTag> { new OpenApiTag { Name = "Authentication" } }, Parameters = new List <OpenApiParameter> { new OpenApiParameter { //Type = "string", Name = "Authorization", Required = false, In = ParameterLocation.Header } } }); swaggerDoc.Paths.Add($"/{Config.TokenLogoutRoute}", logoutItem); }
/// <inheritdoc/> protected override void SetOperations(OpenApiPathItem item) { if (EdmOperationImport.IsActionImport()) { // Each action import is represented as a name/value pair whose name is the service-relative // resource path of the action import prepended with a forward slash, and whose value is a Path // Item Object containing the keyword post with an Operation Object as value that describes // how to invoke the action import. AddOperation(item, OperationType.Post); } else { // Each function import is represented as a name/value pair whose name is the service-relative // resource path of the function import prepended with a forward slash, and whose value is a Path // Item Object containing the keyword get with an Operation Object as value that describes // how to invoke the function import. // so far, <Term Name="ReadRestrictions" Type="Capabilities.ReadRestrictionsType" AppliesTo="EntitySet Singleton FunctionImport"> ReadRestrictionsType read = Context.Model.GetRecord <ReadRestrictionsType>(EdmOperationImport, CapabilitiesConstants.ReadRestrictions); if (read == null || read.IsReadable) { AddOperation(item, OperationType.Get); } } }
private OpenApiPathItem HandlePath(OpenApiPathItem value) { value.Parameters = handleParameters(value.Parameters); foreach (var operation in value.Operations) { switch (operation.Key) { case OperationType.Get: case OperationType.Post: case OperationType.Put: case OperationType.Patch: case OperationType.Delete: case OperationType.Head: case OperationType.Options: if (operation.Value != null) { operation.Value.Parameters = handleParameters(operation.Value.Parameters); } break; } } return(value); }
/// <summary> /// Create partial OpenAPI document based on the provided predicate. /// </summary> /// <param name="source">The target <see cref="OpenApiDocument"/>.</param> /// <param name="predicate">A predicate function.</param> /// <returns>A partial OpenAPI document.</returns> public static OpenApiDocument CreateFilteredDocument(OpenApiDocument source, Func <string, OperationType?, OpenApiOperation, bool> predicate) { // Fetch and copy title, graphVersion and server info from OpenApiDoc var subset = new OpenApiDocument { Info = new OpenApiInfo { Title = source.Info.Title + " - Subset", Description = source.Info.Description, TermsOfService = source.Info.TermsOfService, Contact = source.Info.Contact, License = source.Info.License, Version = source.Info.Version, Extensions = source.Info.Extensions }, Components = new OpenApiComponents { SecuritySchemes = source.Components.SecuritySchemes }, SecurityRequirements = source.SecurityRequirements, Servers = source.Servers }; var results = FindOperations(source, predicate); foreach (var result in results) { OpenApiPathItem pathItem; var pathKey = result.CurrentKeys.Path; if (subset.Paths == null) { subset.Paths = new OpenApiPaths(); pathItem = new OpenApiPathItem(); subset.Paths.Add(pathKey, pathItem); } else { if (!subset.Paths.TryGetValue(pathKey, out pathItem)) { pathItem = new OpenApiPathItem(); subset.Paths.Add(pathKey, pathItem); } } if (result.CurrentKeys.Operation != null) { pathItem.Operations.Add((OperationType)result.CurrentKeys.Operation, result.Operation); } } if (subset.Paths == null) { throw new ArgumentException("No paths found for the supplied parameters."); } CopyReferences(subset); return(subset); }
private OpenApiUrlSpaceNode Attach(IEnumerable <string> segments, OpenApiPathItem pathItem, string layer) { var segment = segments.FirstOrDefault(); if (string.IsNullOrEmpty(segment)) { if (PathItem == null) { PathItem = pathItem; Layer = layer; } return(this); } // If the child segment has already been defined, then insert into it if (Children.ContainsKey(segment)) { return(Children[segment].Attach(segments.Skip(1), pathItem, layer)); } else { var node = new OpenApiUrlSpaceNode(segment); Children[segment] = node; return(node.Attach(segments.Skip(1), pathItem, layer)); } }
/// <summary> /// Constructs an OpenApi document that will mapper the /swagger endpoint to the operation. /// </summary> /// <returns> /// The <see cref="OpenApiDocument"/>. /// </returns> public static OpenApiDocument BuildSwaggerDocument() { var jsonMediaType = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object" } }; var response = new OpenApiResponse(); response.Content.Add("application/json", jsonMediaType); response.Description = "OK"; var operation = new OpenApiOperation { OperationId = SwaggerOperationId, Summary = "View swagger definition for this API", Description = "View swagger definition for this API", }; operation.Responses.Add("200", response); var pathItem = new OpenApiPathItem(); pathItem.Operations.Add(OperationType.Get, operation); return(new OpenApiDocument { Paths = new OpenApiPaths { { "/swagger", pathItem }, }, }); }
public async Task When_yaml_OpenAPI_spec_has_external_schema_refs_they_are_resolved(string relativePath, string docPath, string header) { var path = GetTestDirectory() + relativePath; //// Act OpenApiDocument doc = await OpenApiYamlDocument.FromFileAsync(path); IDictionary <string, OpenApiPathItem> docPaths = doc.Paths; OpenApiPathItem pathItem = docPaths[docPath]; OpenApiOperation operation = pathItem["get"]; IDictionary <string, OpenApiResponse> responses = operation.Responses; OpenApiResponse OK = responses["200"].ActualResponse; OpenApiHeaders OKheaders = OK.Headers; OpenApiResponse Unauthorized = responses["401"].ActualResponse; ////Assert // Header schema loaded correctly from headers.yaml Assert.True(OKheaders.ContainsKey(header)); Assert.NotNull(OKheaders[header]); //Response data loaded correctly from responses.yaml string problemType = "application/problem+json"; Assert.True(Unauthorized.Content.ContainsKey(problemType)); Assert.NotNull(Unauthorized.Content[problemType]); Assert.NotNull(Unauthorized.Schema); }
/// <summary> /// Set the parameters information for <see cref="OpenApiPathItem"/> /// </summary> /// <param name="item">The <see cref="OpenApiPathItem"/>.</param> protected virtual void SetParameters(OpenApiPathItem item) { foreach (var parameter in Path.CreatePathParameters(Context)) { item.Parameters.AppendParameter(parameter); } }
public async Task When_yaml_OpenAPI_spec_has__relative_external_schema_refs_in_subdirs__they_are_resolved(string relativePath) { var path = GetTestDirectory() + relativePath; OpenApiDocument doc = await OpenApiYamlDocument.FromFileAsync(path); IDictionary <string, OpenApiPathItem> docPaths = doc.Paths; OpenApiPathItem pathItem = docPaths["/life-cycles"]; OpenApiOperation operation = pathItem["get"]; IDictionary <string, OpenApiResponse> responses = operation.Responses; OpenApiResponse OK = responses["200"].ActualResponse; var schema = OK.Content["application/json"]; JsonSchemaProperty items = schema.Schema.ActualSchema.ActualProperties["items"]; var innerProperties = items.Item.ActualSchema.ActualProperties; string[] expectedProperties = new string[] { "id", "systemName", "name", "smallImageID", "helpText" }; foreach (string property in expectedProperties) { Assert.True(innerProperties.ContainsKey(property)); } pathItem = docPaths["/ad-hoc-tasks/{adhocTaskId}/execute"]; operation = pathItem["post"]; responses = operation.Responses; OK = responses["200"].ActualResponse; schema = OK.Content["application/json"]; Assert.Equal("status", schema.Schema.ActualDiscriminator); Assert.Equal("Completed", schema.Schema.ActualDiscriminatorObject.Mapping.Keys.First()); Assert.Equal(2, schema.Schema.ActualSchema.ActualProperties["status"].ActualSchema.Enumeration.Count); }
private void ProcessPath(string?apiRootPath, IEnumerable <ContractInfo> contracts, string?key) { _doc.Paths = new OpenApiPaths(); foreach (var contract in contracts) { var roles = _keyRoles.GetRoles(key); var roleMethods = contract.GetMethods(roles); foreach (var contractMethod in roleMethods) { foreach (var route in contractMethod.Route.SwaggerRouts) { var pathItem = new OpenApiPathItem(); foreach (var method in route.HttpMethods) { //AddOperation var operation = _pathProcessor.Process(contractMethod, route, method); pathItem.AddOperation(method.ToOperationType(), operation); } //add a path //_doc.Paths.Add($"{apiRootPath}/{route.Path}", pathItem); AddPath($"{apiRootPath}/{route.Path}", pathItem); } } } }
private void MapPath(RestEaseInterface @interface, string path, OpenApiPathItem pathItem) { foreach (var restEaseInterfaceMethodDetails in pathItem.Operations.Select(o => MapOperationToMappingModel(@interface, path, o.Key.ToString(), o.Value))) { @interface.Methods.Add(restEaseInterfaceMethodDetails); } }
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { foreach (var definition in _definitions) { var pathItem = new OpenApiPathItem(); var operation = _getOperation(pathItem, definition.Method); operation.Responses = new OpenApiResponses(); operation.Parameters = new List <OpenApiParameter>(); foreach (var parameter in definition.Parameters) { if (parameter.In is InBody) { operation.Parameters.Add(new OpenApiParameter { Name = parameter.Name, Schema = new OpenApiSchema { Type = parameter.Type, Example = new OpenApiString(JsonConvert.SerializeObject(parameter.Example)) } }); } else if (parameter.In is InQuery) { operation.Parameters.Add(new OpenApiParameter { Name = parameter.Name, Schema = new OpenApiSchema { Type = parameter.Type, Example = new OpenApiString(JsonConvert.SerializeObject(parameter.Example)) } }); } } foreach (var response in definition.Responses) { operation.Responses.Add(response.StatusCode.ToString(), new OpenApiResponse { Content = new Dictionary <string, OpenApiMediaType> { { "body", new OpenApiMediaType { Schema = new OpenApiSchema { Type = response.Type, Example = new OpenApiString(JsonConvert.SerializeObject(response.Example)) } } } } }); } swaggerDoc.Paths.Add($"/{definition.Path}", pathItem); } }
private OpenApiPathItem HealthPathItem() { var pathItem = new OpenApiPathItem(); pathItem.AddOperation(OperationType.Get, new OpenApiOperation { Tags = new List <OpenApiTag>() { new OpenApiTag() { Name = "Health" } }, OperationId = "Health_Get", Responses = new OpenApiResponses() { ["200"] = new OpenApiResponse { Description = "OK", }, }, }); return(pathItem); }
/// <summary> /// Visits <see cref="OpenApiPathItem"/> and child objects /// </summary> internal void Walk(OpenApiPathItem pathItem) { if (pathItem == null) { return; } if (_pathItemLoop.Contains(pathItem)) { return; // Loop detected, this pathItem has already been walked. } else { _pathItemLoop.Push(pathItem); } _visitor.Visit(pathItem); if (pathItem != null) { Walk(OpenApiConstants.Parameters, () => Walk(pathItem.Parameters)); Walk(pathItem.Operations); } _visitor.Visit(pathItem as IOpenApiExtensible); _pathItemLoop.Pop(); }
/// <inheritdoc/> protected override void SetOperations(OpenApiPathItem item) { ReadRestrictionsType read = Context.Model.GetRecord <ReadRestrictionsType>(EntitySet); if (read == null || (read.ReadByKeyRestrictions == null && read.IsReadable) || (read.ReadByKeyRestrictions != null && read.ReadByKeyRestrictions.IsReadable)) { // If we don't have Read by key read restriction, we should check the set read restrction. AddOperation(item, OperationType.Get); } UpdateRestrictionsType update = Context.Model.GetRecord <UpdateRestrictionsType>(EntitySet); if (update == null || update.IsUpdatable) { AddOperation(item, OperationType.Patch); } DeleteRestrictionsType delete = Context.Model.GetRecord <DeleteRestrictionsType>(EntitySet); if (delete == null || delete.IsDeletable) { AddOperation(item, OperationType.Delete); } }
/// <summary> /// Visits <see cref="OpenApiPathItem"/> and child objects /// </summary> /// <param name="pathItem"></param> internal void Walk(OpenApiPathItem pathItem) { _visitor.Visit(pathItem); Walk(pathItem.Operations); _visitor.Visit(pathItem as IOpenApiExtensible); }
public void EndpointsHaveImplementation(string endpointName) { string key = endpointName .Remove(endpointName.Length - 5, 5) .Remove(0, 1); OpenApiPathItem endpoint = Resources.GetApiPathItem(key); EndpointType endpointType = Resources.GetEndpointType(endpoint); if (endpointType == EndpointType.HardCoded) { var method = typeof(HardCoded).GetMethod(key, BindingFlags.Public | BindingFlags.Static); Assert.IsNotNull(method, "Method {0} missing", endpointName); var parameters = method.GetParameters(); Assert.AreEqual(1, parameters.Length, "Hard-coded endpoint methods must have a single parameter."); Assert.AreEqual(typeof(Dictionary <string, string>), parameters.Single().ParameterType, "Hard-coded endpoint method parameter must be a Dictionary<string, string>."); } else { var queryString = Resources.GetSparql(key); foreach (var parameter in Resources.GetSparqlParameters(endpoint)) { Assert.IsTrue(queryString.Contains($"@{parameter.Name}"), "Parameter @{0} missing in query {1}", parameter.Name, endpointName); } Assert.IsNotNull(queryString, "SPARQL file missing"); } }
void AddPostOperation(IDebuggingHandler handler, OpenApiPathItem item, OpenApiOperation operation) { if (handler.GetType().ImplementsOpenGeneric(typeof(ICanHandlePostRequests <>))) { item.AddOperation(OperationType.Post, operation); } }
public void GetDocs( SwaggerAggregateRoute aggregateRoute, IEnumerable <RouteDocs> routeDocs, OpenApiOperation expected) { IRoutesDocumentationProvider provider = Substitute.For <IRoutesDocumentationProvider>(); provider.GetRouteDocs(Arg.Any <IEnumerable <string> >(), Arg.Any <IEnumerable <RouteOptions> >()) .Returns(routeDocs); var generator = new AggregateRouteDocumentationGenerator( Routes, provider, Substitute.For <IDefinedAggregatorProvider>(), AggregateRouteDocumentationGenerator.DefaultPostProcess, Substitute.For <ISchemaGenerator>()); OpenApiPathItem docs = generator.GenerateDocs(aggregateRoute, new OpenApiDocument()); OpenApiOperation actual = docs.Operations.First().Value; actual.Summary.Should().Be(expected.Summary); actual.Description.Should().Be(expected.Description); actual.Parameters.Should().HaveCount(expected.Parameters.Count); for (int i = 0; i < expected.Parameters.Count; i++) { actual.Parameters[i].Name.Should().Be(expected.Parameters[i].Name); actual.Parameters[i].In.Should().Be(expected.Parameters[i].In); actual.Parameters[i].Description.Should().Be(expected.Parameters[i].Description); } }