diff --git a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs index 9c037889..c2f3c312 100644 --- a/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs +++ b/src/Microsoft.OpenApi.OData.Reader/Common/Utils.cs @@ -345,6 +345,13 @@ internal static bool TryAddPath(this IDictionary pathI if (derivedTypes?.Any() ?? false) { + if (boundEntityType != null && boundEntityType == operationEntityType) + { + // The operation's binding type exactly matches the entity set's type, + // so this is a more specific overload than whatever was added first. + pathItems[pathName] = pathItem; + return true; + } if (boundEntityType != null && !derivedTypes.Contains(boundEntityType)) { Debug.WriteLine($"Duplicate paths present but entity type of binding parameter '{operationEntityType}' " + diff --git a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs index a450bc04..630af975 100644 --- a/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs +++ b/test/Microsoft.OpenAPI.OData.Reader.Tests/Edm/ODataPathProviderTests.cs @@ -7,11 +7,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Validation; +using Microsoft.OpenApi; using Microsoft.OpenApi.OData.Tests; using Xunit; @@ -921,6 +924,83 @@ private static IEdmModel GetNavPropModel(string annotation) return GetEdmModel(template); } + [Fact] + public async Task GetPathsForDerivedTypeDeltaFunctionUsesCorrectReturnType() + { + // Arrange – mirrors the Graph scenario: + // directoryObject (base) has delta with RequiresExplicitBinding + // servicePrincipal (derived) has its own delta + // agentIdentity (derived from servicePrincipal) causes servicePrincipal to have derived types + // Bug: TryAddPath kept the base-type delta for /servicePrincipals/delta() because + // servicePrincipal has derived types. + string csdl = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NS.delta + + + + + +"; + + bool result = CsdlReader.TryParse(XElement.Parse(csdl).CreateReader(), out IEdmModel model, out _); + Assert.True(result); + + var settings = new OpenApiConvertSettings(); + var doc = model.ConvertToOpenApi(settings); + + // Serialize to YAML and verify the response type + using var stream = new MemoryStream(); + await doc.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, "yaml", CancellationToken.None); + stream.Position = 0; + string yaml = await new StreamReader(stream).ReadToEndAsync(); + + // The /servicePrincipals/NS.delta() path should reference servicePrincipal, not directoryObject + Assert.Contains("/servicePrincipals/NS.delta()", yaml); + + // Extract just the path section (up to 'components:' or next top-level key) + int pathIndex = yaml.IndexOf("/servicePrincipals/NS.delta():"); + Assert.True(pathIndex >= 0, "Path /servicePrincipals/NS.delta() not found in YAML output"); + + int componentsIndex = yaml.IndexOf("\ncomponents:", pathIndex); + string pathSection = componentsIndex > 0 + ? yaml.Substring(pathIndex, componentsIndex - pathIndex) + : yaml.Substring(pathIndex); + + // The response schema items $ref should reference servicePrincipal + Assert.Contains("'#/components/schemas/NS.servicePrincipal'", pathSection); + Assert.DoesNotContain("#/components/schemas/NS.directoryObject", pathSection); + } + private static IEdmModel GetEdmModel(string schema) { bool parsed = SchemaReader.TryParse(new XmlReader[] { XmlReader.Create(new StringReader(schema)) }, out IEdmModel parsedModel, out IEnumerable errors);