diff --git a/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs b/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs index f69cfa3dc9..b62148f79b 100644 --- a/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs +++ b/src/Microsoft.OData.Core/Evaluation/ODataConventionalEntityMetadataBuilder.cs @@ -15,6 +15,7 @@ namespace Microsoft.OData.Evaluation using System.Text; using Microsoft.OData.Edm; using Microsoft.OData.Edm.Vocabularies.V1; + using Microsoft.OData.UriParser; #endregion /// @@ -405,10 +406,34 @@ private Uri ComputeEditLink() { Uri uri = this.ResourceMetadataContext.Resource.HasNonComputedId ? this.ResourceMetadataContext.Resource.NonComputedId : this.ComputedId; - Debug.Assert(this.ResourceMetadataContext != null && this.ResourceMetadataContext.TypeContext != null, "this.resourceMetadataContext != null && this.resourceMetadataContext.TypeContext != null"); - if (this.ResourceMetadataContext.ActualResourceTypeName != this.ResourceMetadataContext.TypeContext.NavigationSourceEntityTypeName) + Debug.Assert(this.ResourceMetadataContext != null && this.ResourceMetadataContext.TypeContext != null, + "this.resourceMetadataContext != null && this.resourceMetadataContext.TypeContext != null"); + + string actualResourceTypeName= this.ResourceMetadataContext.ActualResourceTypeName; + + if (actualResourceTypeName != this.ResourceMetadataContext.TypeContext.NavigationSourceEntityTypeName) { - uri = this.UriBuilder.AppendTypeSegment(uri, this.ResourceMetadataContext.ActualResourceTypeName); + IEdmStructuredType actualResourceType = this.ResourceMetadataContext.ActualResourceType; + + // In some scenarios, the resource id will already contain the cast segment. For example, + // when full metadata is requested and a derived entity containing one or more navigation properties + // is being serialized, and the association and navigation links need to be serialized as well, the cast + // segment will already be contained in the resource id. + ODataUriParser odataUriParser = new ODataUriParser(this.MetadataContext.Model, this.MetadataContext.ServiceBaseUri, uri); + ODataPath odataPath = odataUriParser.ParsePath(); + + // Accomodate two scenarios: + // 1. ~/Customers(1)/NS.EnterpriseCustomer + // 2. ~/Customers/NS.EnterpriseCustomer(1) - key expression on type segment + if (!((odataPath.LastSegment is TypeSegment typeSegment1 + && typeSegment1.TargetEdmType == actualResourceType) + || (odataPath.LastSegment is KeySegment + && odataPath.Segments.Count >= 2 + && odataPath.Segments[odataPath.Segments.Count - 2] is TypeSegment typeSegment2 + && typeSegment2.TargetEdmType == actualResourceType))) + { + uri = this.UriBuilder.AppendTypeSegment(uri, actualResourceTypeName); + } } return uri; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalEntityMetadataBuilderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalEntityMetadataBuilderTests.cs index 6ca155aefd..e3a162c19e 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalEntityMetadataBuilderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalEntityMetadataBuilderTests.cs @@ -21,9 +21,15 @@ public class ODataConventionalEntityMetadataBuilderTests private readonly ODataConventionalUriBuilder uriBuilder = new ODataConventionalUriBuilder(DefaultBaseUri, ODataUrlKeyDelimiter.Parentheses); - private readonly TestMetadataContext metadataContext = new TestMetadataContext { GetMetadataDocumentUriFunc = () => MetadataDocumentUri, GetModelFunc = () => TestModel.Model, OperationsBoundToStructuredTypeMustBeContainerQualifiedFunc = type => false }; + private readonly TestMetadataContext metadataContext = new TestMetadataContext + { + GetServiceBaseUriFunc = () => DefaultBaseUri, + GetMetadataDocumentUriFunc = () => MetadataDocumentUri, + GetModelFunc = () => TestModel.Model, + OperationsBoundToStructuredTypeMustBeContainerQualifiedFunc = type => false + }; private ODataResource productEntry; - private Dictionary sinlgeKeyCollection; + private Dictionary singleKeyCollection; private Dictionary multiKeysCollection; private ODataConventionalEntityMetadataBuilder productConventionalEntityMetadataBuilder; private ODataResource derivedMultiKeyMultiEtagMleEntry; @@ -32,20 +38,21 @@ public class ODataConventionalEntityMetadataBuilderTests private ODataConventionalEntityMetadataBuilder containedCollectionProductConventionalEntityMetadataBuilder; private ODataResource containedProductEntry; private ODataConventionalEntityMetadataBuilder containedProductConventionalEntityMetadataBuilder; - private Dictionary containedSinlgeKeyCollection; - private Dictionary containedMultiKeysCollection; + private Dictionary containedSingleKeyCollection; private const string EntitySetName = "Products"; private const string EntityTypeName = "TestModel.Product"; - private const string DerivedEntityTypeName = "TestModel.DerivedProduct"; - private const string DerivedMleEntityTypeName = "TestModel.DerivedMleProduct"; + + private const string MultiKeyEntitySetName = "MultipleKeySet"; + private const string MultiKeyEntityTypeName = "TestModel.MultipleKeyType"; + private const string DerivedMleMultiKeyEntityTypeName = "TestModel.DerivedMleMultiKeyType"; public ODataConventionalEntityMetadataBuilderTests() { #region Product Entry this.productEntry = new ODataResource(); - this.sinlgeKeyCollection = new Dictionary() { { "Id", 42 } }; + this.singleKeyCollection = new Dictionary() { { "Id", 42 } }; this.multiKeysCollection = new Dictionary() { { "KeyA", "keya" }, { "KeyB", 1 } }; TestFeedAndEntryTypeContext productTypeContext = new TestFeedAndEntryTypeContext @@ -63,7 +70,7 @@ public ODataConventionalEntityMetadataBuilderTests() TypeContext = productTypeContext, Resource = this.productEntry, ETagProperties = new[] { new KeyValuePair("Name", "Value") }, - KeyProperties = this.sinlgeKeyCollection, + KeyProperties = this.singleKeyCollection, ActualResourceTypeName = EntityTypeName, SelectedBindableOperations = new IEdmOperation[0], SelectedNavigationProperties = new IEdmNavigationProperty[0], @@ -85,9 +92,9 @@ public ODataConventionalEntityMetadataBuilderTests() this.derivedMultiKeyMultiEtagMleEntry = new ODataResource(); TestFeedAndEntryTypeContext derivedMultiKeyMultiEtagMleTypeContext = new TestFeedAndEntryTypeContext { - NavigationSourceName = EntitySetName, - NavigationSourceEntityTypeName = EntityTypeName, - ExpectedResourceTypeName = DerivedEntityTypeName, + NavigationSourceName = MultiKeyEntitySetName, + NavigationSourceEntityTypeName = MultiKeyEntityTypeName, + ExpectedResourceTypeName = DerivedMleMultiKeyEntityTypeName, IsMediaLinkEntry = true, IsFromCollection = false }; @@ -97,7 +104,7 @@ public ODataConventionalEntityMetadataBuilderTests() Resource = this.derivedMultiKeyMultiEtagMleEntry, ETagProperties = new[] { new KeyValuePair("ETag1", "ETagValue1"), new KeyValuePair("ETag2", "ETagValue2") }, KeyProperties = this.multiKeysCollection, - ActualResourceTypeName = DerivedMleEntityTypeName, + ActualResourceTypeName = DerivedMleMultiKeyEntityTypeName, SelectedBindableOperations = new IEdmOperation[] { action, @@ -106,7 +113,7 @@ public ODataConventionalEntityMetadataBuilderTests() SelectedNavigationProperties = TestModel.ProductWithNavPropsType.NavigationProperties(), SelectedStreamProperties = new Dictionary { - {"Photo", new EdmStructuralProperty(TestModel.ProductType, "Photo", EdmCoreModel.Instance.GetStream( /*isNullable*/true))} + {"Photo", new EdmStructuralProperty(TestModel.MultipleKeyType, "Photo", EdmCoreModel.Instance.GetStream( /*isNullable*/true))} }, }; @@ -118,8 +125,7 @@ public ODataConventionalEntityMetadataBuilderTests() #region Contained Product Entry this.containedCollectionProductEntry = new ODataResource(); - this.containedSinlgeKeyCollection = new Dictionary() { { "Id", 43 } }; - this.containedMultiKeysCollection = new Dictionary() { { "KeyA", "keya" }, { "KeyB", 2 } }; + this.containedSingleKeyCollection = new Dictionary() { { "Id", 43 } }; TestFeedAndEntryTypeContext containedCollectionProductTypeContext = new TestFeedAndEntryTypeContext { @@ -136,7 +142,7 @@ public ODataConventionalEntityMetadataBuilderTests() TypeContext = containedCollectionProductTypeContext, Resource = this.containedCollectionProductEntry, ETagProperties = new[] { new KeyValuePair("Name", "Value") }, - KeyProperties = this.containedSinlgeKeyCollection, + KeyProperties = this.containedSingleKeyCollection, ActualResourceTypeName = EntityTypeName, SelectedBindableOperations = new IEdmOperation[0], SelectedNavigationProperties = new IEdmNavigationProperty[0], @@ -148,8 +154,6 @@ public ODataConventionalEntityMetadataBuilderTests() this.containedCollectionProductEntry.MetadataBuilder.ParentMetadataBuilder = this.productConventionalEntityMetadataBuilder; this.containedProductEntry = new ODataResource(); - this.containedSinlgeKeyCollection = new Dictionary() { { "Id", 43 } }; - this.containedMultiKeysCollection = new Dictionary() { { "KeyA", "keya" }, { "KeyB", 2 } }; TestFeedAndEntryTypeContext containedProductTypeContext = new TestFeedAndEntryTypeContext { @@ -164,9 +168,9 @@ public ODataConventionalEntityMetadataBuilderTests() TestEntryMetadataContext containedProductEntryMetadataContext = new TestEntryMetadataContext { TypeContext = containedProductTypeContext, - Resource = this.containedCollectionProductEntry, + Resource = this.containedProductEntry, ETagProperties = new[] { new KeyValuePair("Name", "Value") }, - KeyProperties = this.containedSinlgeKeyCollection, + KeyProperties = this.containedSingleKeyCollection, ActualResourceTypeName = EntityTypeName, SelectedBindableOperations = new IEdmOperation[0], SelectedNavigationProperties = new IEdmNavigationProperty[0], @@ -228,7 +232,7 @@ public void GetEditLinkWithSingleKey() [Fact] public void GetEditLinkWithMultipleKeys() { - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink(), new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct")); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink(), new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType")); } [Fact] @@ -251,7 +255,7 @@ public void EditLinkShouldContainTypeSegmentIfInstanceTypeIsMoreDerviedThanSet() { // Verify that the last segment of the edit link is the expected type segment. string[] uriSegments = this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink().Segments; - Assert.Equal("TestModel.DerivedMleProduct", uriSegments[uriSegments.Length - 1]); + Assert.Equal("TestModel.DerivedMleMultiKeyType", uriSegments[uriSegments.Length - 1]); } @@ -259,23 +263,23 @@ public void EditLinkShouldContainTypeSegmentIfInstanceTypeIsMoreDerviedThanSet() public void GetEditLinkShouldReturnComputedIdWithTypeCastForDerivedEntity() { Uri id = this.derivedMultiKeyMultiEtagMleEntry.Id; - Uri expectedEditLink = this.uriBuilder.AppendTypeSegment(id, DerivedMleEntityTypeName); + Uri expectedEditLink = this.uriBuilder.AppendTypeSegment(id, DerivedMleMultiKeyEntityTypeName); Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink(), expectedEditLink); } [Fact] public void GetEditLinkShouldReturnNonComputedIdUriWithTypeCastForDerivedEntityWhenNonComputedIdIsSet() { - var id = new Uri("http://anotherodata.org/serviceBase/SomeType('xyz')"); + var id = new Uri("http://odata.org/base/MultipleKeySet(KeyA='xyz',KeyB=2)"); this.derivedMultiKeyMultiEtagMleEntry.Id = id; - Uri expectedEditLink = this.uriBuilder.AppendTypeSegment(id, DerivedMleEntityTypeName); + Uri expectedEditLink = this.uriBuilder.AppendTypeSegment(id, DerivedMleMultiKeyEntityTypeName); Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink(), expectedEditLink); } [Fact] public void GetEditLinkShouldReturnNonComputedIdUriWhenNonComputedIdIsSet() { - var id = new Uri("http://anotherodata.org/serviceBase/SomeType('xyz')"); + var id = new Uri("http://odata.org/base/MultipleKeySet(KeyA='xyz',KeyB=2)"); this.productEntry.Id = id; Assert.Equal(this.productConventionalEntityMetadataBuilder.GetEditLink(), id); } @@ -318,7 +322,7 @@ public void GetEditLinkWithSingleKeyWhenKeyisDecimal() public void GetEditLinkWithMultiKeysWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink(), new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetEditLink(), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType", entitySetInstanceId))); } [Fact] @@ -485,7 +489,7 @@ public void GetIdWithSingleKeyWhenKeyisDecimalAndEntryDoesNotContainIdEditOrRead public void GetIdWithMultiKeysWhenKeyisLongLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetId(), new Uri(string.Format(@"http://odata.org/base/Products({0})", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetId(), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})", entitySetInstanceId))); } #endregion Tests for GetId() @@ -500,7 +504,7 @@ public void GetReadLinkWithSingleKey() [Fact] public void GetReadLinkWithMultipleKeys() { - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetReadLink(), new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct")); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetReadLink(), new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType")); } [Fact] @@ -557,7 +561,7 @@ public void GetReadLinkWithSingleKeyWhenKeyisDecimal() public void GetReadLinkWithMultiKeysWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetReadLink(), new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetReadLink(), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType", entitySetInstanceId))); } [Fact] @@ -683,7 +687,7 @@ public void GetStreamEditLinkForStreamPropertyWhenEntryHasEditLink() public void GetDefaultStreamEditLinkWithMultiKeysWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetStreamEditLink(null), new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/$value", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetStreamEditLink(null), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/$value", entitySetInstanceId))); } #endregion Tests for GetStreamEditLink() @@ -699,8 +703,8 @@ public void ShouldComputeMrForMle() { var mr = this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetMediaResource(); Assert.NotNull(mr); - Assert.Equal(mr.EditLink, new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/$value")); - Assert.Equal(mr.ReadLink, new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/$value")); + Assert.Equal(mr.EditLink, new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/$value")); + Assert.Equal(mr.ReadLink, new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/$value")); } [Fact] @@ -783,7 +787,7 @@ public void GetStreamReadLinkForStreamPropertyWhenEntryHasBothReadLinkAndEditLin public void GetDefaultStreamReadLinkWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetStreamReadLink(null), new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/$value", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetStreamReadLink(null), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/$value", entitySetInstanceId))); } #endregion Tests for GetStreamReadLink() @@ -834,7 +838,7 @@ public void GetNavigationLinkUriWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetNavigationLinkUri("NavigationProperty", null, false), - new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/NavigationProperty", entitySetInstanceId))); + new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/NavigationProperty", entitySetInstanceId))); } #endregion Tests for GetNavigationLinkUri() @@ -879,7 +883,7 @@ public void GetAssociationLinkUriShouldFollowReadLinkWhenEntryHasBothReadLinkAnd public void GetAssociationLinkUriWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetAssociationLinkUri("NavigationProperty", null, false), new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/NavigationProperty/$ref", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetAssociationLinkUri("NavigationProperty", null, false), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/NavigationProperty/$ref", entitySetInstanceId))); } #endregion Tests for GetAssociationLinkUri() @@ -939,7 +943,7 @@ public void GetOperationTargetUriWhenEntryHasEditLink() [Fact] public void GetOperationTargetUriWithInheritance() { - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetOperationTargetUri("OperationName", null, null), new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/OperationName")); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetOperationTargetUri("OperationName", null, null), new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/OperationName")); } [Fact] @@ -965,7 +969,7 @@ public void GetOperationTargetUriWithParameterTypeWhenEntryHasEditLink() [Fact] public void GetOperationTargetUriWithParameterTypeAndInheritance() { - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetOperationTargetUri("OperationName", null, "p1"), new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/OperationName(p1=@p1)")); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetOperationTargetUri("OperationName", null, "p1"), new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/OperationName(p1=@p1)")); } [Fact] @@ -981,7 +985,7 @@ public void GetOperationTargetUriWithParameterTypeAndInheritanceWhenEntryHasEdit public void GetOperationTargetUriWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); - Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetOperationTargetUri("OperationName", null, null), new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/OperationName", entitySetInstanceId))); + Assert.Equal(this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetOperationTargetUri("OperationName", null, null), new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/OperationName", entitySetInstanceId))); } #endregion Tests for GetOperationTargetUri() @@ -1007,8 +1011,8 @@ public void DerivedProductShouldContainComputedNamedStreams() Assert.Equal("Photo", photoProperty.Name); var photo = (ODataStreamReferenceValue)photoProperty.Value; Assert.NotNull(photo); - Assert.Equal(photo.EditLink, new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/Photo")); - Assert.Equal(photo.ReadLink, new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/Photo")); + Assert.Equal(photo.EditLink, new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/Photo")); + Assert.Equal(photo.ReadLink, new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/Photo")); } [Fact] @@ -1038,7 +1042,7 @@ public void DerivedProductShouldContainComputedActions() var action = this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetActions().Single(); Assert.Equal("TestModel.Action", action.Title); Assert.Equal(action.Metadata, new Uri(MetadataDocumentUri, "#TestModel.Action")); - Assert.Equal(action.Target, new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/TestModel.Action")); + Assert.Equal(action.Target, new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/TestModel.Action")); } [Fact] @@ -1046,7 +1050,7 @@ public void GetActionsWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); var action = this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetActions().Single(); - Assert.Equal(action.Target, new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/TestModel.Action", entitySetInstanceId))); + Assert.Equal(action.Target, new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/TestModel.Action", entitySetInstanceId))); } [Fact] @@ -1073,7 +1077,7 @@ public void DerivedProductShouldContainComputedFunctions() var function = this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetFunctions().Single(); Assert.Equal("TestModel.Function", function.Title); Assert.Equal(function.Metadata, new Uri(MetadataDocumentUri, "#TestModel.Function")); - Assert.Equal(function.Target, new Uri("http://odata.org/base/Products(KeyA='keya',KeyB=1)/TestModel.DerivedMleProduct/TestModel.Function")); + Assert.Equal(function.Target, new Uri("http://odata.org/base/MultipleKeySet(KeyA='keya',KeyB=1)/TestModel.DerivedMleMultiKeyType/TestModel.Function")); } [Fact] @@ -1091,7 +1095,7 @@ public void GetFunctionsWhenKeyisLFDM() { var entitySetInstanceId = SetMultiKeyProperties(); var function = this.derivedMultiKeyMultiEtagMleConventionalEntityMetadataBuilder.GetFunctions().Single(); - Assert.Equal(function.Target, new Uri(string.Format(@"http://odata.org/base/Products({0})/TestModel.DerivedMleProduct/TestModel.Function", entitySetInstanceId))); + Assert.Equal(function.Target, new Uri(string.Format(@"http://odata.org/base/MultipleKeySet({0})/TestModel.DerivedMleMultiKeyType/TestModel.Function", entitySetInstanceId))); } #endregion Tests for computed Functions @@ -1112,7 +1116,7 @@ public void TestSingletonIdAndEditLink() TypeContext = singletonEntryTypeContext, Resource = new ODataResource(), ETagProperties = new[] { new KeyValuePair("Name", "Value") }, - KeyProperties = this.sinlgeKeyCollection, + KeyProperties = this.singleKeyCollection, ActualResourceTypeName = "BossType", SelectedBindableOperations = new IEdmOperation[0], SelectedNavigationProperties = new IEdmNavigationProperty[0], @@ -1149,18 +1153,16 @@ private Uri SetDerivedProductEntryEditLink() private void SetSingleKeyPropertie(string name, object value) { - this.sinlgeKeyCollection.Clear(); - this.sinlgeKeyCollection.Add(name, value); + this.singleKeyCollection.Clear(); + this.singleKeyCollection.Add(name, value); } private string SetMultiKeyProperties() { this.multiKeysCollection.Clear(); - this.multiKeysCollection.Add("LongId", -1L); - this.multiKeysCollection.Add("FloatId", 1.0f); - this.multiKeysCollection.Add("DoubleId", -1.0d); - this.multiKeysCollection.Add("DecimalId", -1.0m); - return "LongId=-1,FloatId=1,DoubleId=-1.0,DecimalId=-1.0"; + this.multiKeysCollection.Add("KeyA", "keya"); + this.multiKeysCollection.Add("KeyB", 1); + return "KeyA='keya',KeyB=1"; } } diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalUriBuilderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalUriBuilderTests.cs index e468cfc44d..c5fc9bf5fe 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalUriBuilderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/ODataConventionalUriBuilderTests.cs @@ -31,7 +31,7 @@ public ODataConventionalUriBuilderTests() this.model = TestModel.BuildDefaultTestModel(); this.defaultProductInstance = TestModel.BuildDefaultProductValue(TestModel.GetEntityType(this.model, "TestModel.Product")); - this.defaultMultipleKeyInstance = TestModel.BuildDefaultMultipleKeyValue(this.model); + this.defaultMultipleKeyInstance = TestModel.BuildDefaultMultipleKeyValue(TestModel.GetEntityType(this.model, "TestModel.MultipleKeyType")); this.idPropertyList = new Dictionary() { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/TestModel.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/TestModel.cs index 4d9a22f15c..99835fd0d1 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/TestModel.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/Evaluation/TestModel.cs @@ -19,8 +19,10 @@ internal class TestModel public IEdmEntityType ProductType { get; set; } public IEdmEntityType DerivedProductType { get; set; } public IEdmEntityType MultipleKeyType { get; set; } + public IEdmEntityType DerivedMleMultiKeyType { get; set; } public IEdmEntityType ProductWithNavPropsType { get; set; } public IEdmStructuredValue OneMultipleKeyValue { get; set; } + public IEdmStructuredValue OneDerivedMleMultiKeyValue { get; set; } public IEdmStructuredValue OneProductValue { get; set; } public IEdmStructuredValue OneDerivedProductValue { get; set; } public IEdmStructuredValue OneProductWithNavPropsValue { get; set; } @@ -64,19 +66,22 @@ public static TestModel Initialize() model.AddElement(multipleKeyType); result.MultipleKeysSet = defaultContainer.AddEntitySet("MultipleKeySet", multipleKeyType); + var derivedMleMultiKeyType = new EdmEntityType("TestModel", "DerivedMleMultiKeyType", multipleKeyType); + result.DerivedMleMultiKeyType = derivedMleMultiKeyType; + EdmEntityType productTypeWithNavProps = new EdmEntityType("TestModel", "ProductWithNavProps", productType); result.ProductWithNavPropsType = productTypeWithNavProps; productTypeWithNavProps.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() { Name = "RelatedProducts", - Target = productType, + Target = multipleKeyType, TargetMultiplicity = EdmMultiplicity.Many }); productTypeWithNavProps.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() { Name = "RelatedDerivedProduct", - Target = derivedProductType, + Target = derivedMleMultiKeyType, TargetMultiplicity = EdmMultiplicity.One }); @@ -96,8 +101,9 @@ public static TestModel Initialize() result.OneProductValue = BuildDefaultProductValue(productType); result.OneDerivedProductValue = BuildDefaultProductValue(derivedProductType); - result.OneMultipleKeyValue = BuildDefaultMultipleKeyValue(model); - result.OneProductWithNavPropsValue = BuildDefaultProductValue(productTypeWithNavProps); + result.OneMultipleKeyValue = BuildDefaultMultipleKeyValue(multipleKeyType); + result.OneDerivedMleMultiKeyValue = BuildDefaultMultipleKeyValue(derivedMleMultiKeyType); + result.OneProductWithNavPropsValue = BuildDefaultMultipleKeyValue(multipleKeyType); return result; } @@ -113,10 +119,10 @@ internal static IEdmStructuredValue BuildDefaultProductValue(IEdmEntityType enti }); } - internal static IEdmStructuredValue BuildDefaultMultipleKeyValue(IEdmModel defaultModel) + internal static IEdmStructuredValue BuildDefaultMultipleKeyValue(IEdmEntityType entityType) { return new EdmStructuredValueSimulator( - GetEntityType(defaultModel, "TestModel.MultipleKeyType"), + entityType, new Dictionary { { "KeyA", new EdmStringConstant(EdmCoreModel.Instance.GetString(false), "keya") }, diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataDerivedResourceTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataDerivedResourceTests.cs new file mode 100644 index 0000000000..1b4799fe25 --- /dev/null +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataDerivedResourceTests.cs @@ -0,0 +1,224 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using Microsoft.OData.Edm; +using Microsoft.OData.Evaluation; +using Microsoft.OData.UriParser; +using Xunit; + +namespace Microsoft.OData.Tests +{ + public class ODataDerivedResourceTests : IClassFixture + { + private readonly ODataResource derivedResource; + + public ODataDerivedResourceTests(ODataDerivedResourceTestsFixture fixture) + { + derivedResource = fixture.InitDerivedResource(); + } + + [Fact] + public void DerivedResourceWithComputedIdShouldHaveExpectedEditLink() + { + var editLink = derivedResource.MetadataBuilder.GetEditLink(); + + Assert.NotNull(editLink); + Assert.Equal("http://tempuri.org/Customers(1)/NS.EnterpriseCustomer", editLink.AbsoluteUri); + } + + [Theory] + [InlineData("Customers(1)", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers(1)/NS.EnterpriseCustomer", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers/NS.EnterpriseCustomer(1)", "Customers/NS.EnterpriseCustomer(1)")] + public void DerivedResourceWithNonComputedIdShouldHaveExpectedEditLink(string odataPath, string expected) + { + derivedResource.Id = new Uri($"http://tempuri.org/{odataPath}"); + var editLink = derivedResource.MetadataBuilder.GetEditLink(); + + Assert.NotNull(editLink); + Assert.Equal($"http://tempuri.org/{expected}", editLink.AbsoluteUri); + } + + [Fact] + public void DerivedResourceWithComputedIdShouldHaveExpectedReadLink() + { + var readLink = derivedResource.MetadataBuilder.GetReadLink(); + + Assert.NotNull(readLink); + Assert.Equal("http://tempuri.org/Customers(1)/NS.EnterpriseCustomer", readLink.AbsoluteUri); + } + + [Theory] + [InlineData("Customers(1)", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers(1)/NS.EnterpriseCustomer", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers/NS.EnterpriseCustomer(1)", "Customers/NS.EnterpriseCustomer(1)")] + public void DerivedResourceWithNonComputedIdShouldHaveExpectedReadLink(string odataPath, string expected) + { + derivedResource.Id = new Uri($"http://tempuri.org/{odataPath}"); + var readLink = derivedResource.MetadataBuilder.GetReadLink(); + + Assert.NotNull(readLink); + Assert.Equal($"http://tempuri.org/{expected}", readLink.AbsoluteUri); + } + + [Fact] + public void DerivedResourceWithComputedIdShouldHaveExpectedNavigationLink() + { + var navigationLink = derivedResource.MetadataBuilder.GetNavigationLinkUri("RelationshipManager", navigationLinkUrl: null, hasNestedResourceInfoUrl: false); + + Assert.NotNull(navigationLink); + Assert.Equal("http://tempuri.org/Customers(1)/NS.EnterpriseCustomer/RelationshipManager", navigationLink.AbsoluteUri); + } + + [Theory] + [InlineData("Customers(1)", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers(1)/NS.EnterpriseCustomer", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers/NS.EnterpriseCustomer(1)", "Customers/NS.EnterpriseCustomer(1)")] + public void DerivedResourceWithNonComputedIdShouldHaveExpectedNavigationLink(string odataPath, string expected) + { + derivedResource.Id = new Uri($"http://tempuri.org/{odataPath}"); + var navigationLink = derivedResource.MetadataBuilder.GetNavigationLinkUri( + "RelationshipManager", + navigationLinkUrl: null, + hasNestedResourceInfoUrl: false); + + Assert.NotNull(navigationLink); + Assert.Equal($"http://tempuri.org/{expected}/RelationshipManager", navigationLink.AbsoluteUri); + } + + [Fact] + public void DerivedResourceWithComputedIdShouldHaveExpectedAssociationLink() + { + var associationLink = derivedResource.MetadataBuilder.GetAssociationLinkUri( + "RelationshipManager", + associationLinkUrl: null, + hasAssociationLinkUrl: false); + + Assert.NotNull(associationLink); + Assert.Equal("http://tempuri.org/Customers(1)/NS.EnterpriseCustomer/RelationshipManager/$ref", associationLink.AbsoluteUri); + } + + [Theory] + [InlineData("Customers(1)", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers(1)/NS.EnterpriseCustomer", "Customers(1)/NS.EnterpriseCustomer")] + [InlineData("Customers/NS.EnterpriseCustomer(1)", "Customers/NS.EnterpriseCustomer(1)")] + public void DerivedResourceWithNonComputedIdShouldHaveExpectedAssociationLink(string odataPath, string expected) + { + derivedResource.Id = new Uri($"http://tempuri.org/{odataPath}"); + var associationLink = derivedResource.MetadataBuilder.GetAssociationLinkUri( + "RelationshipManager", + associationLinkUrl: null, + hasAssociationLinkUrl: false); + + Assert.NotNull(associationLink); + Assert.Equal($"http://tempuri.org/{expected}/RelationshipManager/$ref", associationLink.AbsoluteUri); + } + } + + public class ODataDerivedResourceTestsFixture : IDisposable + { + private const string baseUri = "http://tempuri.org"; + private static Uri serviceRoot = new Uri(baseUri); + private readonly ODataMetadataContext metadataContext; + private readonly ODataUriBuilder odataUriBuilder; + private readonly IODataResourceTypeContext resourceTypeContext; + private readonly ODataResourceSerializationInfo resourceSerializationInfo; + private readonly EdmEntityType enterpriseCustomerEntityType; + + public ODataDerivedResourceTestsFixture() + { + var model = new EdmModel(); + + var customerEntityType = new EdmEntityType("NS", "Customer"); + customerEntityType.AddKeys(customerEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + model.AddElement(customerEntityType); + + var employeeEntityType = new EdmEntityType("NS", "Employee"); + employeeEntityType.AddKeys(employeeEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + model.AddElement(employeeEntityType); + + this.enterpriseCustomerEntityType = new EdmEntityType("NS", "EnterpriseCustomer", customerEntityType); + model.AddElement(this.enterpriseCustomerEntityType); + + var relationshipManagerNavigationProperty = this.enterpriseCustomerEntityType.AddUnidirectionalNavigation( + new EdmNavigationPropertyInfo + { + Name = "RelationshipManager", + Target = employeeEntityType, + TargetMultiplicity = EdmMultiplicity.ZeroOrOne + }); + + var entityContainer = new EdmEntityContainer("NS", "Default"); + model.AddElement(entityContainer); + + var customersEntitySet = entityContainer.AddEntitySet("Customers", customerEntityType); + var employeesEntitySet = entityContainer.AddEntitySet("Employees", employeeEntityType); + + customersEntitySet.AddNavigationTarget(relationshipManagerNavigationProperty, employeesEntitySet); + + this.resourceSerializationInfo = new ODataResourceSerializationInfo + { + NavigationSourceName = "Customers", + NavigationSourceEntityTypeName = "NS.Customer", + ExpectedTypeName = "NS.EnterpriseCustomer" + }; + + var odataUri = new ODataUri { ServiceRoot = serviceRoot, Path = new ODataPath(new EntitySetSegment(customersEntitySet)) }; + this.odataUriBuilder = new ODataConventionalUriBuilder(serviceRoot, ODataUrlKeyDelimiter.Parentheses); + this.resourceTypeContext = ODataResourceTypeContext.Create( + this.resourceSerializationInfo, + customersEntitySet, + customerEntityType, + this.enterpriseCustomerEntityType, + throwIfMissingTypeInfo: true); + this.metadataContext = new ODataMetadataContext( + isResponse: true, + model: model, + metadataDocumentUri: new Uri(serviceRoot, "$metadata"), + odataUri: new ODataUriSlim(odataUri)); + } + + public ODataResource InitDerivedResource() + { + var derivedResource = new ODataResource + { + TypeName = "NS.EnterpriseCustomer", + Properties = new List + { + new ODataProperty + { + Name = "Id", + Value = 1, + SerializationInfo = new ODataPropertySerializationInfo { PropertyKind = ODataPropertyKind.Key } + } + } + }; + + var resourceMetadataContext = ODataResourceMetadataContext.Create( + derivedResource, + this.resourceTypeContext, + this.resourceSerializationInfo, + this.enterpriseCustomerEntityType, + this.metadataContext, + selectedProperties: new SelectedPropertiesNode(SelectedPropertiesNode.SelectionType.EntireSubtree), + metadataSelector: null); + + derivedResource.MetadataBuilder = new ODataConventionalEntityMetadataBuilder( + resourceMetadataContext, + this.metadataContext, + this.odataUriBuilder); + + return derivedResource; + } + + public void Dispose() + { + + } + } +} diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataNavigationLinkTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataNavigationLinkTests.cs index 1cc6c036dd..5e29789014 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataNavigationLinkTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ODataNavigationLinkTests.cs @@ -5,6 +5,7 @@ //--------------------------------------------------------------------- using System; +using Microsoft.OData.Edm; using Microsoft.OData.Evaluation; using Microsoft.OData.Tests.Evaluation; using Xunit; @@ -21,6 +22,21 @@ public class ODataNavigationLinkTests public ODataNavigationLinkTests() { + var model = new EdmModel(); + + var baseEntityType = new EdmEntityType("ns", "BaseType"); + baseEntityType.AddKeys(baseEntityType.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32)); + baseEntityType.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String); + model.AddElement(baseEntityType); + + var derivedEntityType = new EdmEntityType("ns", "DerivedType", baseEntityType); + model.AddElement(derivedEntityType); + + var entityContainer = new EdmEntityContainer("ns", "Default"); + model.AddElement(entityContainer); + + entityContainer.AddEntitySet("Set", baseEntityType); + this.navigationLink = new ODataNestedResourceInfo(); var entry = new ODataResource @@ -35,7 +51,11 @@ public ODataNavigationLinkTests() var serializationInfo = new ODataResourceSerializationInfo { NavigationSourceName = "Set", NavigationSourceEntityTypeName = "ns.BaseType", ExpectedTypeName = "ns.BaseType" }; var typeContext = ODataResourceTypeContext.Create(serializationInfo, null, null, null, true); - var metadataContext = new TestMetadataContext(); + var metadataContext = new TestMetadataContext + { + GetModelFunc = () => model, + GetServiceBaseUriFunc = () => ServiceUri + }; var entryMetadataContext = ODataResourceMetadataContext.Create(entry, typeContext, serializationInfo, null, metadataContext, new SelectedPropertiesNode(SelectedPropertiesNode.SelectionType.EntireSubtree), null); var metadataBuilder = new ODataConventionalEntityMetadataBuilder(entryMetadataContext, metadataContext, new ODataConventionalUriBuilder(ServiceUri, ODataUrlKeyDelimiter.Parentheses));