Continue on the PathItemHandler and test cases

This commit is contained in:
Sam Xu 2018-08-27 17:11:50 -07:00
parent 5d9614464f
commit 009ca7fef3
6 changed files with 324 additions and 69 deletions

View file

@ -177,59 +177,13 @@ namespace Microsoft.OpenApi.OData.Capabilities
}
/// <summary>
/// Gets the capablities from the <see cref="IEdmModel"/> for the given <see cref="IEdmVocabularyAnnotatable"/>.
/// Create the capabiliites restriction
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="target">The target.</param>
/// <param name="kind">Thye Capabilites kind.</param>
/// <returns>The capabilities restrictions or null.</returns>
public static ICapablitiesRestrictions GetCapabilities(this IEdmModel model, IEdmVocabularyAnnotatable target, CapabilitesTermKind kind)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
lock (_objectLock)
{
if (!ReferenceEquals(_savedModel, model))
{
if (_capabilitesRestrictions != null)
{
_capabilitesRestrictions.Clear();
}
_savedModel = model;
}
if (_capabilitesRestrictions == null)
{
_capabilitesRestrictions = new Dictionary<IEdmVocabularyAnnotatable, IDictionary<CapabilitesTermKind, ICapablitiesRestrictions>>();
}
ICapablitiesRestrictions restriction;
if (_capabilitesRestrictions.TryGetValue(target, out IDictionary<CapabilitesTermKind, ICapablitiesRestrictions> value))
{
// Here means we visited target before and we are sure that the value is not null.
if (value.TryGetValue(kind, out restriction))
{
return restriction;
}
else
{
restriction = CreateCapabilitesRestrictions(model, target, kind);
value[kind] = restriction;
return restriction;
}
}
// It's first time to query this target, create new dictionary and restriction.
value = new Dictionary<CapabilitesTermKind, ICapablitiesRestrictions>();
_capabilitesRestrictions[target] = value;
restriction = CreateCapabilitesRestrictions(model, target, kind);
value[kind] = restriction;
return restriction;
}
}
private static ICapablitiesRestrictions CreateCapabilitesRestrictions(this IEdmModel model, IEdmVocabularyAnnotatable target, CapabilitesTermKind kind)
/// <param name="target">The Target.</param>
/// <param name="kind">The Capabiliites kind.</param>
/// <returns>The <see cref="ICapablitiesRestrictions"/>.</returns>
public static ICapablitiesRestrictions CreateCapabilitesRestrictions(this IEdmModel model, IEdmVocabularyAnnotatable target, CapabilitesTermKind kind)
{
Debug.Assert(model != null);
Debug.Assert(target != null);
@ -314,5 +268,58 @@ namespace Microsoft.OpenApi.OData.Capabilities
return capabilitiesRestrictions;
}
/// <summary>
/// Gets the capablities from the <see cref="IEdmModel"/> for the given <see cref="IEdmVocabularyAnnotatable"/>.
/// </summary>
/// <param name="model">The Edm model.</param>
/// <param name="target">The target.</param>
/// <param name="kind">Thye Capabilites kind.</param>
/// <returns>The capabilities restrictions or null.</returns>
private static ICapablitiesRestrictions GetCapabilities(this IEdmModel model, IEdmVocabularyAnnotatable target, CapabilitesTermKind kind)
{
Utils.CheckArgumentNull(model, nameof(model));
Utils.CheckArgumentNull(target, nameof(target));
lock (_objectLock)
{
if (!ReferenceEquals(_savedModel, model))
{
if (_capabilitesRestrictions != null)
{
_capabilitesRestrictions.Clear();
}
_savedModel = model;
}
if (_capabilitesRestrictions == null)
{
_capabilitesRestrictions = new Dictionary<IEdmVocabularyAnnotatable, IDictionary<CapabilitesTermKind, ICapablitiesRestrictions>>();
}
ICapablitiesRestrictions restriction;
if (_capabilitesRestrictions.TryGetValue(target, out IDictionary<CapabilitesTermKind, ICapablitiesRestrictions> value))
{
// Here means we visited target before and we are sure that the value is not null.
if (value.TryGetValue(kind, out restriction))
{
return restriction;
}
else
{
restriction = CreateCapabilitesRestrictions(model, target, kind);
value[kind] = restriction;
return restriction;
}
}
// It's first time to query this target, create new dictionary and restriction.
value = new Dictionary<CapabilitesTermKind, ICapablitiesRestrictions>();
_capabilitesRestrictions[target] = value;
restriction = CreateCapabilitesRestrictions(model, target, kind);
value[kind] = restriction;
return restriction;
}
}
}
}

View file

@ -81,7 +81,7 @@ namespace Microsoft.OpenApi.OData.Capabilities
{
return RestrictedProperties != null ?
RestrictedProperties.Where(a => a.NavigationProperty == navigationPropertyPath)
.Any(a => a.Navigability != null && a.Navigability.Value == NavigationType.None) :
.Any(b => b.Navigability != null && b.Navigability.Value == NavigationType.None) :
false;
}

View file

@ -3,6 +3,7 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// ------------------------------------------------------------
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.OData.Edm;
@ -48,10 +49,13 @@ namespace Microsoft.OpenApi.OData.PathItem
target = NavigationSource as IEdmSingleton;
}
string navigationPropertyPath = String.Join("/",
Path.Segments.OfType<ODataNavigationPropertySegment>().Select(e => e.NavigationProperty.Name));
// contaiment: Get / (Post - Collection | Patch - Single)
// non-containment: only Get
NavigationRestrictions navigation = Context.Model.GetNavigationRestrictions(target);
if (navigation == null || navigation.IsNavigable)
if (navigation == null || !navigation.IsRestrictedProperty(navigationPropertyPath))
{
AddOperation(item, OperationType.Get);
}
@ -62,8 +66,9 @@ namespace Microsoft.OpenApi.OData.PathItem
{
if (LastSegmentIsKeySegment)
{
// Need to check this scenario is valid or not?
UpdateRestrictions update = Context.Model.GetUpdateRestrictions(target);
if (update == null || update.IsUpdatable)
if (update == null || !update.IsNonUpdatableNavigationProperty(navigationPropertyPath))
{
AddOperation(item, OperationType.Patch);
}
@ -71,7 +76,7 @@ namespace Microsoft.OpenApi.OData.PathItem
else
{
InsertRestrictions insert = Context.Model.GetInsertRestrictions(target);
if (insert == null || insert.IsInsertable)
if (insert == null || !insert.IsNonInsertableNavigationProperty(navigationPropertyPath))
{
AddOperation(item, OperationType.Post);
}
@ -80,7 +85,7 @@ namespace Microsoft.OpenApi.OData.PathItem
else
{
UpdateRestrictions update = Context.Model.GetUpdateRestrictions(target);
if (update == null || update.IsUpdatable)
if (update == null || !update.IsNonUpdatableNavigationProperty(navigationPropertyPath))
{
AddOperation(item, OperationType.Patch);
}

View file

@ -12,7 +12,7 @@ using Microsoft.OData.Edm.Csdl;
using Microsoft.OData.Edm.Validation;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Tests;
using Microsoft.OpenApi.OData.Properties;
using Xunit;
namespace Microsoft.OpenApi.OData.PathItem.Tests
@ -36,25 +36,25 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
Assert.Throws<ArgumentNullException>("path",
() => _pathItemHandler.CreatePathItem(new ODataContext(EdmCoreModel.Instance), path: null));
}
/*
[Fact]
public void CreatePathItemThrowsForNonNavigationPropertyPath()
{
// Arrange
IEdmModel model = GetEdmModel(annotation: "");
IEdmModel model = EntitySetPathItemHandlerTests.GetEdmModel(annotation: "");
ODataContext context = new ODataContext(model);
var entitySet = model.EntityContainer.FindEntitySet("Customers");
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
var path = new ODataPath(new ODataNavigationSourceSegment(entitySet), new ODataKeySegment(entitySet.EntityType()));
Assert.Equal(ODataPathKind.Entity, path.Kind); // guard
var path = new ODataPath(new ODataNavigationSourceSegment(entitySet));
Assert.Equal(ODataPathKind.EntitySet, path.Kind); // guard
// Act
Action test = () => _pathItemHandler.CreatePathItem(context, path);
// Assert
var exception = Assert.Throws<InvalidOperationException>(test);
Assert.Equal(String.Format(SRResource.InvalidPathKindForPathItemHandler, "EntitySetPathItemHandler", path.Kind), exception.Message);
}*/
Assert.Equal(String.Format(SRResource.InvalidPathKindForPathItemHandler, "NavigationPropertyPathItemHandler", path.Kind), exception.Message);
}
[Theory]
[InlineData(true, true, new OperationType[] { OperationType.Get, OperationType.Patch })]
@ -64,9 +64,9 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
public void CreateCollectionNavigationPropertyPathItemReturnsCorrectPathItem(bool containment, bool keySegment, OperationType[] expected)
{
// Arrange
IEdmModel model = EdmModelHelper.GraphBetaModel;
IEdmModel model = GetEdmModel("");
ODataContext context = new ODataContext(model);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("users");
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
IEdmEntityType entityType = entitySet.EntityType();
@ -100,9 +100,9 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
public void CreateSingleNavigationPropertyPathItemReturnsCorrectPathItem(bool containment, OperationType[] expected)
{
// Arrange
IEdmModel model = EdmModelHelper.GraphBetaModel;
IEdmModel model = GetEdmModel("");
ODataContext context = new ODataContext(model);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("users");
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
IEdmEntityType entityType = entitySet.EntityType();
@ -125,6 +125,155 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
Assert.Equal(expected, pathItem.Operations.Select(o => o.Key));
}
public static IEnumerable<object[]> CollectionNavigationPropertyData
{
get
{
IList<string> navigationPropertyPaths = new List<string>
{
"ContainedOrders",
"Orders",
"ContainedOrders/ContainedOrderLines",
"ContainedOrders/OrderLines",
"Orders/ContainedOrderLines",
"Orders/OrderLines",
"ContainedMyOrder/ContainedOrderLines",
"ContainedMyOrder/OrderLines",
"MyOrder/ContainedOrderLines",
"MyOrder/OrderLines"
};
foreach (var path in navigationPropertyPaths)
{
foreach (var enableAnnotation in new[] { true, false })
{
yield return new object[] { enableAnnotation, path };
}
}
}
}
public static IEnumerable<object[]> SingleNavigationPropertyData
{
get
{
IList<string> navigationPropertyPaths = new List<string>
{
"ContainedMyOrder",
"MyOrder",
"ContainedOrders/ContainedMyOrderLine",
"ContainedOrders/MyOrderLine",
"Orders/ContainedMyOrderLine",
"Orders/MyOrderLine",
"ContainedMyOrder/ContainedMyOrderLine",
"ContainedMyOrder/MyOrderLine",
"MyOrder/ContainedMyOrderLine",
"MyOrder/MyOrderLine"
};
foreach (var path in navigationPropertyPaths)
{
foreach (var enableAnnotation in new[] { true, false })
{
yield return new object[] { enableAnnotation, path };
}
}
}
}
[Theory]
[MemberData(nameof(CollectionNavigationPropertyData))]
public void CreatePathItemForNavigationPropertyAndInsertRestrictions(bool hasRestrictions, string navigationPropertyPath)
{
// Arrange
string annotation = String.Format(@"
<Annotation Term=""Org.OData.Capabilities.V1.InsertRestrictions"">
<Record>
<PropertyValue Property=""NonInsertableNavigationProperties"" >
<Collection>
<NavigationPropertyPath>{0}</NavigationPropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>", navigationPropertyPath);
IEdmModel model = GetEdmModel(hasRestrictions ? annotation : "");
ODataContext context = new ODataContext(model);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
ODataPath path = CreatePath(entitySet, navigationPropertyPath, false);
// Act
var pathItem = _pathItemHandler.CreatePathItem(context, path);
// Assert
Assert.NotNull(pathItem);
Assert.NotNull(pathItem.Operations);
Assert.NotEmpty(pathItem.Operations);
bool isContainment = path.Segments.OfType<ODataNavigationPropertySegment>().Last().NavigationProperty.ContainsTarget;
OperationType[] expected;
if (!isContainment || hasRestrictions)
{
expected = new[] { OperationType.Get };
}
else
{
expected = new[] { OperationType.Get, OperationType.Post };
}
Assert.Equal(expected, pathItem.Operations.Select(o => o.Key));
}
[Theory]
[MemberData(nameof(CollectionNavigationPropertyData))]
[MemberData(nameof(SingleNavigationPropertyData))]
public void CreatePathItemForNavigationPropertyAndUpdateRestrictions(bool hasRestrictions, string navigationPropertyPath)
{
// Arrange
string annotation = String.Format(@"
<Annotation Term=""Org.OData.Capabilities.V1.UpdateRestrictions"">
<Record>
<PropertyValue Property=""NonUpdatableNavigationProperties"" >
<Collection>
<NavigationPropertyPath>{0}</NavigationPropertyPath>
</Collection>
</PropertyValue>
</Record>
</Annotation>", navigationPropertyPath);
IEdmModel model = GetEdmModel(hasRestrictions ? annotation : "");
ODataContext context = new ODataContext(model);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
ODataPath path = CreatePath(entitySet, navigationPropertyPath, true);
// Act
var pathItem = _pathItemHandler.CreatePathItem(context, path);
// Assert
Assert.NotNull(pathItem);
Assert.NotNull(pathItem.Operations);
Assert.NotEmpty(pathItem.Operations);
bool isContainment = path.Segments.OfType<ODataNavigationPropertySegment>().Last().NavigationProperty.ContainsTarget;
OperationType[] expected;
if (!isContainment || hasRestrictions)
{
expected = new[] { OperationType.Get };
}
else
{
expected = new[] { OperationType.Get, OperationType.Patch };
}
Assert.Equal(expected, pathItem.Operations.Select(o => o.Key));
}
public static IEdmModel GetEdmModel(string annotation)
{
const string template = @"<edmx:Edmx Version=""4.0"" xmlns:edmx=""http://docs.oasis-open.org/odata/ns/edmx"">
@ -135,13 +284,26 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
<PropertyRef Name=""ID"" />
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
<NavigationProperty Name=""DirectOrders"" Type=""Collection(NS.Order)"" />
<NavigationProperty Name=""ContainedOrders"" Type=""Collection(NS.Order)"" ContainsTarget=""true"" />
<NavigationProperty Name=""Orders"" Type=""Collection(NS.Order)"" />
<NavigationProperty Name=""ContainedMyOrder"" Type=""NS.Order"" Nullable=""false"" ContainsTarget=""true"" />
<NavigationProperty Name=""MyOrder"" Type=""NS.Order"" Nullable=""false"" />
</EntityType>
<EntityType Name=""Order"">
<Key>
<PropertyRef Name=""ID"" />
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
<NavigationProperty Name=""ContainedOrderLines"" Type=""Collection(NS.OrderLine)"" ContainsTarget=""true"" />
<NavigationProperty Name=""OrderLines"" Type=""Collection(NS.OrderLine)"" />
<NavigationProperty Name=""ContainedMyOrderLine"" Type=""NS.OrderLine"" Nullable=""false"" ContainsTarget=""true"" />
<NavigationProperty Name=""MyOrderLine"" Type=""NS.OrderLine"" Nullable=""false"" />
</EntityType>
<EntityType Name=""OrderLine"">
<Key>
<PropertyRef Name=""ID"" />
</Key>
<Property Name=""ID"" Type=""Edm.Int32"" Nullable=""false"" />
</EntityType>
<EntityContainer Name =""Default"">
<EntitySet Name=""Customers"" EntityType=""NS.Customer"" />
@ -153,6 +315,7 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
</Schema>
</edmx:DataServices>
</edmx:Edmx>";
string modelText = string.Format(template, annotation);
IEdmModel model;
@ -162,5 +325,45 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
Assert.True(result);
return model;
}
private static ODataPath CreatePath(IEdmNavigationSource navigationSource, string navigationPropertyPath, bool single)
{
Assert.NotNull(navigationSource);
Assert.NotNull(navigationPropertyPath);
IEdmEntityType previousEntityType = navigationSource.EntityType();
ODataPath path = new ODataPath(new ODataNavigationSourceSegment(navigationSource), new ODataKeySegment(previousEntityType));
string[] npPaths = navigationPropertyPath.Split('/');
IEdmNavigationProperty previousProperty = null;
foreach (string npPath in npPaths)
{
IEdmNavigationProperty property = previousEntityType.DeclaredNavigationProperties().FirstOrDefault(p => p.Name == npPath);
Assert.NotNull(property);
if (previousProperty != null)
{
if (previousProperty.TargetMultiplicity() == EdmMultiplicity.Many)
{
path.Push(new ODataKeySegment(previousProperty.ToEntityType()));
}
}
path.Push(new ODataNavigationPropertySegment(property));
previousProperty = property;
previousEntityType = property.ToEntityType();
}
if (single)
{
if (previousProperty.TargetMultiplicity() == EdmMultiplicity.Many)
{
path.Push(new ODataKeySegment(previousProperty.ToEntityType()));
}
}
return path;
}
}
}

View file

@ -8,6 +8,7 @@ using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Properties;
using Microsoft.OpenApi.OData.Tests;
using Xunit;
@ -33,6 +34,25 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
() => _pathItemHandler.CreatePathItem(new ODataContext(EdmCoreModel.Instance), path: null));
}
[Fact]
public void CreatePathItemThrowsForNonOperationImportPath()
{
// Arrange
IEdmModel model = EntitySetPathItemHandlerTests.GetEdmModel(annotation: "");
ODataContext context = new ODataContext(model);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
var path = new ODataPath(new ODataNavigationSourceSegment(entitySet));
Assert.Equal(ODataPathKind.EntitySet, path.Kind); // guard
// Act
Action test = () => _pathItemHandler.CreatePathItem(context, path);
// Assert
var exception = Assert.Throws<InvalidOperationException>(test);
Assert.Equal(String.Format(SRResource.InvalidPathKindForPathItemHandler, "OperationImportPathItemHandler", path.Kind), exception.Message);
}
[Theory]
[InlineData("GetNearestAirport", OperationType.Get)]
[InlineData("ResetDataSource", OperationType.Post)]

View file

@ -8,6 +8,7 @@ using System.Linq;
using Microsoft.OData.Edm;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.OData.Edm;
using Microsoft.OpenApi.OData.Properties;
using Microsoft.OpenApi.OData.Tests;
using Xunit;
@ -33,6 +34,25 @@ namespace Microsoft.OpenApi.OData.PathItem.Tests
() => _pathItemHandler.CreatePathItem(new ODataContext(EdmCoreModel.Instance), path: null));
}
[Fact]
public void CreatePathItemThrowsForNonOperationPath()
{
// Arrange
IEdmModel model = EntitySetPathItemHandlerTests.GetEdmModel(annotation: "");
ODataContext context = new ODataContext(model);
IEdmEntitySet entitySet = model.EntityContainer.FindEntitySet("Customers");
Assert.NotNull(entitySet); // guard
var path = new ODataPath(new ODataNavigationSourceSegment(entitySet));
Assert.Equal(ODataPathKind.EntitySet, path.Kind); // guard
// Act
Action test = () => _pathItemHandler.CreatePathItem(context, path);
// Assert
var exception = Assert.Throws<InvalidOperationException>(test);
Assert.Equal(String.Format(SRResource.InvalidPathKindForPathItemHandler, "OperationPathItemHandler", path.Kind), exception.Message);
}
[Theory]
[InlineData("GetFriendsTrips", "People", OperationType.Get)]
[InlineData("ShareTrip", "People", OperationType.Post)]