diff --git a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs index 18c1791e..49ff1ef9 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Edm/ODataPathProvider.cs @@ -184,14 +184,44 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource } // media entity + RetrieveMediaEntityStreamPaths(entityType, path); + + // navigation property + foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties()) + { + if (CanFilter(np)) + { + RetrieveNavigationPropertyPaths(np, path); + } + } + + if (entitySet != null) + { + path.Pop(); // end of entity + } + + path.Pop(); // end of navigation source. + Debug.Assert(path.Any() == false); + } + + /// + /// Retrieves the paths for a media entity stream. + /// + /// The entity type. + /// The current OData path. + private void RetrieveMediaEntityStreamPaths(IEdmEntityType entityType, ODataPath currentPath) + { + Debug.Assert(entityType != null); + Debug.Assert(currentPath != null); + bool createValuePath = true; foreach (IEdmStructuralProperty sp in entityType.DeclaredStructuralProperties()) { if (sp.Type.AsPrimitive().IsStream()) { - path.Push(new ODataStreamPropertySegment(sp.Name)); - AppendPath(path.Clone()); - path.Pop(); + currentPath.Push(new ODataStreamPropertySegment(sp.Name)); + AppendPath(currentPath.Clone()); + currentPath.Pop(); } if (sp.Name.Equals("content", System.StringComparison.OrdinalIgnoreCase)) @@ -205,27 +235,10 @@ private void RetrieveNavigationSourcePaths(IEdmNavigationSource navigationSource */ if (createValuePath && entityType.HasStream) { - path.Push(new ODataStreamContentSegment()); - AppendPath(path.Clone()); - path.Pop(); + currentPath.Push(new ODataStreamContentSegment()); + AppendPath(currentPath.Clone()); + currentPath.Pop(); } - - // navigation property - foreach (IEdmNavigationProperty np in entityType.DeclaredNavigationProperties()) - { - if (CanFilter(np)) - { - RetrieveNavigationPropertyPaths(np, path); - } - } - - if (entitySet != null) - { - path.Pop(); // end of entity - } - - path.Pop(); // end of navigation source. - Debug.Assert(path.Any() == false); } /// @@ -284,6 +297,10 @@ private void RetrieveNavigationPropertyPaths(IEdmNavigationProperty navigationPr } } } + + // Get possible navigation property stream paths + RetrieveMediaEntityStreamPaths(navEntityType, currentPath); + if (navigationProperty.TargetMultiplicity() == EdmMultiplicity.Many) { currentPath.Pop(); diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs index 5233ff25..9c87c52f 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/EntitySetPostOperationHandler.cs @@ -43,40 +43,13 @@ protected override void SetBasicInfo(OpenApiOperation operation) /// protected override void SetRequestBody(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForRequestBody) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = EntitySet.EntityType().FullName() - } - }; - } - // The requestBody field contains a Request Body Object for the request body // that references the schema of the entity set’s entity type in the global schemas. operation.RequestBody = new OpenApiRequestBody { Required = true, Description = "New entity", - Content = new Dictionary - { - { - Constants.ApplicationJsonMediaType, new OpenApiMediaType - { - Schema = schema - } - } - } + Content = GetContentDescription() }; base.SetRequestBody(operation); @@ -85,25 +58,6 @@ protected override void SetRequestBody(OpenApiOperation operation) /// protected override void SetResponses(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForResponses) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = EntitySet.EntityType().FullName() - } - }; - } - operation.Responses = new OpenApiResponses { { @@ -111,16 +65,7 @@ protected override void SetResponses(OpenApiOperation operation) new OpenApiResponse { Description = "Created entity", - Content = new Dictionary - { - { - Constants.ApplicationJsonMediaType, - new OpenApiMediaType - { - Schema = schema - } - } - } + Content = GetContentDescription() } } }; @@ -159,5 +104,63 @@ protected override void AppendCustomParameters(OpenApiOperation operation) AppendCustomParameters(operation, insert.CustomHeaders, ParameterLocation.Header); } } + + /// + /// Get the entity content description. + /// + /// The entity content description. + private IDictionary GetContentDescription() + { + OpenApiSchema schema = GetEntitySchema(); + var content = new Dictionary(); + + if (EntitySet.EntityType().HasStream) + { + // TODO: Read the AcceptableMediaType annotation from model + content.Add(Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } + }); + } + + content.Add(Constants.ApplicationJsonMediaType, new OpenApiMediaType + { + Schema = schema + }); + + return content; + } + + /// + /// Get the entity schema. + /// + /// The entity schema. + private OpenApiSchema GetEntitySchema() + { + OpenApiSchema schema = null; + + if (Context.Settings.EnableDerivedTypesReferencesForRequestBody) + { + schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); + } + + if (schema == null) + { + schema = new OpenApiSchema + { + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet.EntityType().FullName() + } + }; + } + + return schema; + } } } diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs index fa6cf701..d0c8fa41 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityGetOperationHandler.cs @@ -17,7 +17,7 @@ namespace Microsoft.OpenApi.OData.Operation /// /// Retrieve a media content for an Entity /// - internal class MediaEntityGetOperationHandler : EntitySetOperationHandler + internal class MediaEntityGetOperationHandler : MediaEntityOperationalHandler { /// public override OperationType OperationType => OperationType.Get; @@ -25,16 +25,31 @@ internal class MediaEntityGetOperationHandler : EntitySetOperationHandler /// protected override void SetBasicInfo(OpenApiOperation operation) { - string typeName = EntitySet.EntityType().Name; - // Summary - operation.Summary = $"Get media content for {typeName} from {EntitySet.Name}"; + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.Summary = $"Get media content for {typeName} from {EntitySet.Name}"; + } + else + { + operation.Summary = $"Get media content for the navigation property {NavigationProperty.Name} from {NavigationSource.Name}"; + } // OperationId if (Context.Settings.EnableOperationId) { string identifier = Path.LastSegment.Kind == ODataSegmentKind.StreamContent ? "Content" : Path.LastSegment.Identifier; - operation.OperationId = EntitySet.Name + "." + typeName + ".Get" + Utils.UpperFirstChar(identifier); + + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = $"{EntitySet.Name}.{typeName}.Get{Utils.UpperFirstChar(identifier)}"; + } + else // Singleton + { + operation.OperationId = GetOperationId("Get", identifier); + } } base.SetBasicInfo(operation); @@ -43,22 +58,6 @@ protected override void SetBasicInfo(OpenApiOperation operation) /// protected override void SetResponses(OpenApiOperation operation) { - OpenApiSchema schema = null; - - if (Context.Settings.EnableDerivedTypesReferencesForResponses) - { - schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model); - } - - if (schema == null) - { - schema = new OpenApiSchema - { - Type = "string", - Format = "binary" - }; - } - operation.Responses = new OpenApiResponses { { @@ -72,7 +71,11 @@ protected override void SetResponses(OpenApiOperation operation) Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType { - Schema = schema + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } } } } @@ -86,7 +89,9 @@ protected override void SetResponses(OpenApiOperation operation) /// protected override void SetSecurity(OpenApiOperation operation) { - ReadRestrictionsType read = Context.Model.GetRecord(EntitySet, CapabilitiesConstants.ReadRestrictions); + ReadRestrictionsType read = EntitySet != null + ? Context.Model.GetRecord(EntitySet, CapabilitiesConstants.ReadRestrictions) + : Context.Model.GetRecord(Singleton, CapabilitiesConstants.ReadRestrictions); if (read == null) { return; diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs new file mode 100644 index 00000000..e1c7afdd --- /dev/null +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityOperationalHandler.cs @@ -0,0 +1,114 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------ + +using Microsoft.OData.Edm; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; +using Microsoft.OpenApi.OData.Edm; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.OpenApi.OData.Operation +{ + /// + /// Base class for operation of media entity. + /// + internal abstract class MediaEntityOperationalHandler : NavigationPropertyOperationHandler + { + /// + /// Gets/sets the . + /// + protected IEdmEntitySet EntitySet { get; private set; } + + /// + /// Gets the . + /// + protected IEdmSingleton Singleton { get; private set; } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + // The first segment will either be an entity set navigation source or a singleton navigation source. + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet; + + if (EntitySet == null) + { + // Singleton + base.Initialize(context, path); + Singleton = NavigationSource as IEdmSingleton; + } + } + + /// + protected override void SetTags(OpenApiOperation operation) + { + if (EntitySet == null) + { + // Singleton + base.SetTags(operation); + } + else // Entityset + { + string tagIdentifier = EntitySet.Name + "." + EntitySet.EntityType().Name; + + OpenApiTag tag = new OpenApiTag + { + Name = tagIdentifier + }; + + // Use an extension for TOC (Table of Content) + tag.Extensions.Add(Constants.xMsTocType, new OpenApiString("page")); + + operation.Tags.Add(tag); + + Context.AppendTag(tag); + } + } + + /// + protected override void SetExtensions(OpenApiOperation operation) + { + base.SetExtensions(operation); + } + + /// + /// Retrieves the operation Id for a navigation property stream path. + /// + /// The http method identifier name. + /// The stream segment identifier name. + /// + protected string GetOperationId(string prefix, string identifier) + { + Utils.CheckArgumentNull(prefix, nameof(prefix)); + Utils.CheckArgumentNull(identifier, nameof(identifier)); + + IList items = new List + { + NavigationSource.Name + }; + + var lastpath = Path.Segments.Last(c => c is ODataStreamContentSegment || c is ODataStreamPropertySegment); + foreach (var segment in Path.Segments.Skip(1)) + { + if (segment == lastpath) + { + items.Add(prefix + Utils.UpperFirstChar(identifier)); + break; + } + else + { + if (segment is ODataNavigationPropertySegment npSegment) + { + items.Add(npSegment.NavigationProperty.Name); + } + } + } + + return string.Join(".", items); + } + } +} diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs index a8ca7cf9..a6a33a68 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/MediaEntityPutOperationHandler.cs @@ -17,7 +17,7 @@ namespace Microsoft.OpenApi.OData.Operation /// /// Update a media content for an Entity /// - internal class MediaEntityPutOperationHandler : EntitySetOperationHandler + internal class MediaEntityPutOperationHandler : MediaEntityOperationalHandler { /// public override OperationType OperationType => OperationType.Put; @@ -25,16 +25,31 @@ internal class MediaEntityPutOperationHandler : EntitySetOperationHandler /// protected override void SetBasicInfo(OpenApiOperation operation) { - string typeName = EntitySet.EntityType().Name; - // Summary - operation.Summary = $"Update media content for {typeName} in {EntitySet.Name}"; + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.Summary = $"Update media content for {typeName} in {EntitySet.Name}"; + } + else + { + operation.Summary = $"Update media content for the navigation property {NavigationProperty.Name} in {NavigationSource.Name}"; + } // OperationId if (Context.Settings.EnableOperationId) { string identifier = Path.LastSegment.Kind == ODataSegmentKind.StreamContent ? "Content" : Path.LastSegment.Identifier; - operation.OperationId = EntitySet.Name + "." + typeName + ".Update" + Utils.UpperFirstChar(identifier); + + if (EntitySet != null) + { + string typeName = EntitySet.EntityType().Name; + operation.OperationId = $"{EntitySet.Name}.{typeName}.Update{Utils.UpperFirstChar(identifier)}"; + } + else + { + operation.OperationId = GetOperationId("Update", identifier); + } } base.SetBasicInfo(operation); @@ -54,8 +69,11 @@ protected override void SetRequestBody(OpenApiOperation operation) { schema = new OpenApiSchema { - Type = "string", - Format = "binary" + Reference = new OpenApiReference + { + Type = ReferenceType.Schema, + Id = EntitySet != null ? EntitySet.EntityType().FullName() : Singleton.EntityType().FullName() + } }; } @@ -67,6 +85,16 @@ protected override void SetRequestBody(OpenApiOperation operation) { { Constants.ApplicationOctetStreamMediaType, new OpenApiMediaType + { + Schema = new OpenApiSchema + { + Type = "string", + Format = "binary" + } + } + }, + { + Constants.ApplicationJsonMediaType, new OpenApiMediaType { Schema = schema } @@ -92,7 +120,9 @@ protected override void SetResponses(OpenApiOperation operation) /// protected override void SetSecurity(OpenApiOperation operation) { - UpdateRestrictionsType update = Context.Model.GetRecord(EntitySet, CapabilitiesConstants.UpdateRestrictions); + UpdateRestrictionsType update = EntitySet != null + ? Context.Model.GetRecord(EntitySet, CapabilitiesConstants.UpdateRestrictions) + : Context.Model.GetRecord(Singleton, CapabilitiesConstants.UpdateRestrictions); if (update == null || update.Permissions == null) { return; diff --git a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs index dab1643f..c183fa26 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Operation/NavigationPropertyOperationHandler.cs @@ -62,7 +62,8 @@ protected override void Initialize(ODataContext context, ODataPath path) NavigationProperty = path.OfType().Last().NavigationProperty; NavigationPropertyPath = string.Join("/", - Path.Segments.Where(s => !(s is ODataKeySegment || s is ODataNavigationSourceSegment)).Select(e => e.Identifier)); + Path.Segments.Where(s => !(s is ODataKeySegment || s is ODataNavigationSourceSegment + || s is ODataStreamContentSegment || s is ODataStreamPropertySegment)).Select(e => e.Identifier)); IEdmEntitySet entitySet = NavigationSource as IEdmEntitySet; IEdmSingleton singleton = NavigationSource as IEdmSingleton; @@ -115,7 +116,7 @@ protected override void SetTags(OpenApiOperation operation) } } } - + string name = string.Join(".", items); OpenApiTag tag = new OpenApiTag { diff --git a/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs b/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs index 24d75c5c..d7ec6c34 100644 --- a/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs +++ b/src/Microsoft.OpenApi.OData.Reader/PathItem/MediaEntityPathItemHandler.cs @@ -3,6 +3,7 @@ // Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. // ------------------------------------------------------------ +using Microsoft.OData.Edm; using Microsoft.OpenApi.Models; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Vocabulary.Capabilities; @@ -12,15 +13,28 @@ namespace Microsoft.OpenApi.OData.PathItem /// /// Create a for a media entity. /// - internal class MediaEntityPathItemHandler : EntitySetPathItemHandler + internal class MediaEntityPathItemHandler : PathItemHandler { /// protected override ODataPathKind HandleKind => ODataPathKind.MediaEntity; + /// + /// Gets the entity set. + /// + protected IEdmEntitySet EntitySet { get; private set; } + + /// + /// Gets the singleton. + /// + protected IEdmSingleton Singleton { get; private set; } + /// protected override void SetOperations(OpenApiPathItem item) { - ReadRestrictionsType read = Context.Model.GetRecord(EntitySet); + ReadRestrictionsType read = EntitySet != null + ? Context.Model.GetRecord(EntitySet) + : Context.Model.GetRecord(Singleton); + if (read == null || (read.ReadByKeyRestrictions == null && read.IsReadable) || (read.ReadByKeyRestrictions != null && read.ReadByKeyRestrictions.IsReadable)) @@ -28,11 +42,29 @@ protected override void SetOperations(OpenApiPathItem item) AddOperation(item, OperationType.Get); } - UpdateRestrictionsType update = Context.Model.GetRecord(EntitySet); + UpdateRestrictionsType update = EntitySet != null + ? Context.Model.GetRecord(EntitySet) + : Context.Model.GetRecord(Singleton); + if (update == null || update.IsUpdatable) { AddOperation(item, OperationType.Put); } } + + /// + protected override void Initialize(ODataContext context, ODataPath path) + { + base.Initialize(context, path); + + // The first segment could be an entity set segment or a singleton segment. + ODataNavigationSourceSegment navigationSourceSegment = path.FirstSegment as ODataNavigationSourceSegment; + + EntitySet = navigationSourceSegment.NavigationSource as IEdmEntitySet; + if (EntitySet == null) + { + Singleton = navigationSourceSegment.NavigationSource as IEdmSingleton; + } + } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs index a740a7d9..e189e94a 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs @@ -44,7 +44,7 @@ public void GetPathsForGraphBetaModelReturnsAllPaths() // Assert Assert.NotNull(paths); - Assert.Equal(4457, paths.Count()); + Assert.Equal(4544, paths.Count()); } [Fact] @@ -192,7 +192,7 @@ public void GetPathsWithContainedNavigationPropertytWorks() "; - + string entitySet = @""; IEdmModel model = GetEdmModel(entityType, entitySet); ODataPathProvider provider = new ODataPathProvider(); @@ -229,21 +229,21 @@ public void GetPathsWithStreamPropertyAndWithEntityHasStreamWorks(bool hasStream if (hasStream && !streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)) { - Assert.Equal(4, paths.Count()); - Assert.Equal(new[] { "/Todos", "/Todos({Id})", "/Todos({Id})/$value", "/Todos({Id})/Logo" }, + Assert.Equal(7, paths.Count()); + Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/$value", "/Todos({Id})/Logo" }, paths.Select(p => p.GetPathItemName())); } else if ((hasStream && streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase)) || (!hasStream && streamPropName.Equals("Content", StringComparison.OrdinalIgnoreCase))) { - Assert.Equal(3, paths.Count()); - Assert.Equal(new[] { "/Todos", "/Todos({Id})", "/Todos({Id})/Content" }, + Assert.Equal(6, paths.Count()); + Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/Content" }, paths.Select(p => p.GetPathItemName())); } else // !hasStream && !streamPropName.Equals("Content") { - Assert.Equal(3, paths.Count()); - Assert.Equal(new[] { "/Todos", "/Todos({Id})", "/Todos({Id})/Logo" }, + Assert.Equal(6, paths.Count()); + Assert.Equal(new[] { "/me", "/me/photo", "/me/photo/$value", "/Todos", "/Todos({Id})", "/Todos({Id})/Logo"}, paths.Select(p => p.GetPathItemName())); } } @@ -282,9 +282,17 @@ private static IEdmModel GetEdmModel(bool hasStream, string streamPropName) - - - + + + + + + + + + + + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs index 6529d9f3..e921b39e 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetGetOperationHandlerTests.cs @@ -326,12 +326,12 @@ public void CreateEntitySetGetOperationReturnsSecurityForReadRestrictions(bool e } } - public static IEdmModel GetEdmModel(string annotation) + public static IEdmModel GetEdmModel(string annotation, bool hasStream = false) { const string template = @" - + @@ -341,12 +341,12 @@ public static IEdmModel GetEdmModel(string annotation) - {0} + {1} "; - string modelText = string.Format(template, annotation); + string modelText = string.Format(template, hasStream, annotation); IEdmModel model; IEnumerable errors; diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs index ad15568d..8f5fb295 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/EntitySetPostOperationHandlerTests.cs @@ -6,6 +6,7 @@ using Microsoft.OData.Edm; using Microsoft.OpenApi.Extensions; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.OData.Common; using Microsoft.OpenApi.OData.Edm; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -17,12 +18,14 @@ public class EntitySetPostOperationHandlerTests private EntitySetPostOperationHandler _operationHandler = new EntitySetPostOperationHandler(); [Theory] - [InlineData(true)] - [InlineData(false)] - public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOperationId) + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOperationId, bool hasStream) { // Arrange - IEdmModel model = EntitySetGetOperationHandlerTests.GetEdmModel(""); + IEdmModel model = EntitySetGetOperationHandlerTests.GetEdmModel("", hasStream); IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers"); OpenApiConvertSettings settings = new OpenApiConvertSettings { @@ -47,6 +50,25 @@ public void CreateEntitySetPostOperationReturnsCorrectOperation(bool enableOpera Assert.NotNull(post.Responses); Assert.Equal(2, post.Responses.Count); + if (hasStream) + { + // TODO: Read the AcceptableMediaType annotation from model + Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationOctetStreamMediaType)); + Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationJsonMediaType)); + + Assert.NotNull(post.RequestBody); + // TODO: Read the AcceptableMediaType annotation from model + Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationOctetStreamMediaType)); + Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationJsonMediaType)); + } + else + { + Assert.True(post.Responses["201"].Content.ContainsKey(Constants.ApplicationJsonMediaType)); + + Assert.NotNull(post.RequestBody); + Assert.True(post.RequestBody.Content.ContainsKey(Constants.ApplicationJsonMediaType)); + } + if (enableOperationId) { Assert.Equal("Customers.Customer.CreateCustomer", post.OperationId); diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs index 1e9f2522..9d6bff92 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityGetOperationHandlerTests.cs @@ -30,7 +30,9 @@ public void CreateMediaEntityGetOperationReturnsCorrectOperation(bool enableOper ODataContext context = new ODataContext(model, settings); IEdmEntitySet todos = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton me = model.EntityContainer.FindSingleton("me"); Assert.NotNull(todos); + Assert.NotNull(me); IEdmEntityType todo = model.SchemaElements.OfType().First(c => c.Name == "Todo"); IEdmStructuralProperty sp = todo.DeclaredStructuralProperties().First(c => c.Name == "Logo"); @@ -38,27 +40,45 @@ public void CreateMediaEntityGetOperationReturnsCorrectOperation(bool enableOper new ODataKeySegment(todos.EntityType()), new ODataStreamPropertySegment(sp.Name)); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + // Act var getOperation = _operationalHandler.CreateOperation(context, path); + var getOperation2 = _operationalHandler.CreateOperation(context, path2); // Assert Assert.NotNull(getOperation); + Assert.NotNull(getOperation2); Assert.Equal("Get media content for Todo from Todos", getOperation.Summary); + Assert.Equal("Get media content for the navigation property photo from me", getOperation2.Summary); Assert.NotNull(getOperation.Tags); + Assert.NotNull(getOperation2.Tags); + var tag = Assert.Single(getOperation.Tags); + var tag2 = Assert.Single(getOperation2.Tags); Assert.Equal("Todos.Todo", tag.Name); + Assert.Equal("me.profilePhoto", tag2.Name); Assert.NotNull(getOperation.Responses); + Assert.NotNull(getOperation2.Responses); Assert.Equal(2, getOperation.Responses.Count); + Assert.Equal(2, getOperation2.Responses.Count); Assert.Equal(new[] { "200", "default" }, getOperation.Responses.Select(r => r.Key)); + Assert.Equal(new[] { "200", "default" }, getOperation2.Responses.Select(r => r.Key)); if (enableOperationId) { Assert.Equal("Todos.Todo.GetLogo", getOperation.OperationId); + Assert.Equal("me.photo.GetContent", getOperation2.OperationId); } else { Assert.Null(getOperation.OperationId); + Assert.Null(getOperation2.OperationId); } } @@ -74,9 +94,17 @@ public static IEdmModel GetEdmModel() - - - + + + + + + + + + + + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs index 14fe92d4..88533c86 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Operation/MediaEntityPutOperationHandlerTests.cs @@ -28,6 +28,7 @@ public void CreateEntityPutOperationReturnsCorrectOperation(bool enableOperation ODataContext context = new ODataContext(model, settings); IEdmEntitySet todos = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton me = model.EntityContainer.FindSingleton("me"); Assert.NotNull(todos); IEdmEntityType todo = model.SchemaElements.OfType().First(c => c.Name == "Todo"); @@ -36,27 +37,45 @@ public void CreateEntityPutOperationReturnsCorrectOperation(bool enableOperation new ODataKeySegment(todos.EntityType()), new ODataStreamPropertySegment(sp.Name)); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(me), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + // Act var getOperation = _operationalHandler.CreateOperation(context, path); + var getOperation2 = _operationalHandler.CreateOperation(context, path2); // Assert Assert.NotNull(getOperation); + Assert.NotNull(getOperation2); Assert.Equal("Update media content for Todo in Todos", getOperation.Summary); + Assert.Equal("Update media content for the navigation property photo in me", getOperation2.Summary); Assert.NotNull(getOperation.Tags); + Assert.NotNull(getOperation2.Tags); + var tag = Assert.Single(getOperation.Tags); + var tag2 = Assert.Single(getOperation2.Tags); Assert.Equal("Todos.Todo", tag.Name); + Assert.Equal("me.profilePhoto", tag2.Name); Assert.NotNull(getOperation.Responses); + Assert.NotNull(getOperation2.Responses); Assert.Equal(2, getOperation.Responses.Count); + Assert.Equal(2, getOperation2.Responses.Count); Assert.Equal(new[] { "204", "default" }, getOperation.Responses.Select(r => r.Key)); + Assert.Equal(new[] { "204", "default" }, getOperation2.Responses.Select(r => r.Key)); if (enableOperationId) { Assert.Equal("Todos.Todo.UpdateLogo", getOperation.OperationId); + Assert.Equal("me.photo.UpdateContent", getOperation2.OperationId); } else { Assert.Null(getOperation.OperationId); + Assert.Null(getOperation2.OperationId); } } } diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs index 53f164f4..ee20c584 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/PathItem/MediaEntityPathItemHandlerTests.cs @@ -59,8 +59,10 @@ public void CreateMediaEntityPathItemReturnsCorrectItem() // Arrange IEdmModel model = GetEdmModel(""); ODataContext context = new ODataContext(model); - var entitySet = model.EntityContainer.FindEntitySet("Todos"); + IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton singleton = model.EntityContainer.FindSingleton("me"); Assert.NotNull(entitySet); // guard + Assert.NotNull(singleton); IEdmEntityType entityType = entitySet.EntityType(); IEdmStructuralProperty sp = entityType.DeclaredStructuralProperties().First(c => c.Name == "Logo"); @@ -68,14 +70,30 @@ public void CreateMediaEntityPathItemReturnsCorrectItem() new ODataKeySegment(entityType), new ODataStreamPropertySegment(sp.Name)); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(singleton), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + // Act var pathItem = _pathItemHandler.CreatePathItem(context, path); + var pathItem2 = _pathItemHandler.CreatePathItem(context, path2); + + // Assert + Assert.NotNull(pathItem); + Assert.NotNull(pathItem2); Assert.NotNull(pathItem.Operations); + Assert.NotNull(pathItem2.Operations); Assert.NotEmpty(pathItem.Operations); + Assert.NotEmpty(pathItem2.Operations); Assert.Equal(2, pathItem.Operations.Count); + Assert.Equal(2, pathItem2.Operations.Count); Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Put }, pathItem.Operations.Select(o => o.Key)); + Assert.Equal(new OperationType[] { OperationType.Get, OperationType.Put }, + pathItem2.Operations.Select(o => o.Key)); } [Theory] @@ -127,9 +145,10 @@ private void VerifyPathItemOperationsForStreamPropertySegment(string annotation, Assert.NotNull(entitySet); // guard IEdmEntityType entityType = entitySet.EntityType(); + IEdmStructuralProperty sp = entityType.DeclaredStructuralProperties().First(c => c.Name == "Logo"); ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entityType), - new ODataStreamContentSegment()); + new ODataStreamPropertySegment(sp.Name)); // Act var pathItem = _pathItemHandler.CreatePathItem(context, path); @@ -148,23 +167,35 @@ private void VerifyPathItemOperationsForStreamContentSegment(string annotation, IEdmModel model = GetEdmModel(annotation); ODataContext context = new ODataContext(model); IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Todos"); + IEdmSingleton singleton = model.EntityContainer.FindSingleton("me"); Assert.NotNull(entitySet); // guard + Assert.NotNull(singleton); IEdmEntityType entityType = entitySet.EntityType(); - IEdmStructuralProperty sp = entityType.DeclaredStructuralProperties().First(c => c.Name == "Logo"); + IEdmEntityType user = model.SchemaElements.OfType().First(c => c.Name == "user"); + IEdmNavigationProperty navProperty = user.DeclaredNavigationProperties().First(c => c.Name == "photo"); + ODataPath path2 = new ODataPath(new ODataNavigationSourceSegment(singleton), + new ODataNavigationPropertySegment(navProperty), + new ODataStreamContentSegment()); + ODataPath path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entityType), - new ODataStreamPropertySegment(sp.Name)); + new ODataStreamContentSegment()); // Act var pathItem = _pathItemHandler.CreatePathItem(context, path); + var pathItem2 = _pathItemHandler.CreatePathItem(context, path2); // Assert Assert.NotNull(pathItem); + Assert.NotNull(pathItem2); Assert.NotNull(pathItem.Operations); + Assert.NotNull(pathItem2.Operations); Assert.NotEmpty(pathItem.Operations); + Assert.NotEmpty(pathItem2.Operations); Assert.Equal(expected, pathItem.Operations.Select(e => e.Key)); + Assert.Equal(expected, pathItem2.Operations.Select(e => e.Key)); } private IEdmModel GetEdmModel(string annotation) @@ -180,11 +211,22 @@ private IEdmModel GetEdmModel(string annotation) - - + + + + + + + + + + - - {0} + + {0} + + + {0}