From 33ef19796ebc1e7947acb20e59b46697159fdf6b Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Fri, 3 Jan 2025 20:05:41 +0300 Subject: [PATCH 1/5] Added SingletonQueryTests --- .../Helpers/ODataMessageReaderTestsHelper.cs | 235 +++++++++ .../Server/SingletonTestsController.cs | 134 +++++ .../Tests/SingletonQueryTests.cs | 474 ++++++++++++++++++ 3 files changed, 843 insertions(+) create mode 100644 test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs create mode 100644 test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs create mode 100644 test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs diff --git a/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs b/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs new file mode 100644 index 0000000000..241bd0b457 --- /dev/null +++ b/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs @@ -0,0 +1,235 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Client.E2E.TestCommon.Common; +using Microsoft.OData.Edm; +using Xunit; + +namespace Microsoft.OData.Client.E2E.TestCommon.Helpers; + +public class ODataMessageReaderTestsHelper +{ + private readonly Uri BaseUri; + private readonly IEdmModel Model; + private readonly HttpClient Client; + private const string IncludeAnnotation = "odata.include-annotations"; + + public ODataMessageReaderTestsHelper(Uri baseUri, IEdmModel model, HttpClient client) + { + this.BaseUri = baseUri; + this.Model = model; + this.Client = client; + } + + /// + /// Queries resource entries asynchronously based on the provided query text and MIME type. + /// + /// The query text to append to the base URI. + /// The MIME type to set in the request header. + /// A task that represents the asynchronous operation. The task result contains a list of . + public async Task> QueryResourceEntriesAsync(string queryText, string mimeType) + + { + ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; + var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); + + var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); + requestMessage.SetHeader("Accept", mimeType); + requestMessage.SetHeader("Prefer", string.Format("{0}={1}", IncludeAnnotation, "*")); + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(200, responseMessage.StatusCode); + + var entries = new List(); + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) + { + var reader = await messageReader.CreateODataResourceReaderAsync(); + while (await reader.ReadAsync()) + { + if (reader.State == ODataReaderState.ResourceEnd && reader.Item is ODataResource odataResource) + { + entries.Add(odataResource); + } + } + Assert.Equal(ODataReaderState.Completed, reader.State); + } + } + + return entries; + } + + /// + /// Queries resource sets asynchronously based on the provided query text and MIME type. + /// + /// The query text to append to the base URI. + /// The MIME type to set in the request header. + /// A task that represents the asynchronous operation. The task result contains a list of . + public async Task> QueryResourceSetsAsync(string queryText, string mimeType) + + { + ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; + var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); + + var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); + requestMessage.SetHeader("Accept", mimeType); + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(200, responseMessage.StatusCode); + + var entries = new List(); + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) + { + var reader = await messageReader.CreateODataResourceSetReaderAsync(); + while (await reader.ReadAsync()) + { + if (reader.State == ODataReaderState.ResourceEnd) + { + if (reader.Item is ODataResource odataResource) + { + entries.Add(odataResource); + } + } + } + Assert.Equal(ODataReaderState.Completed, reader.State); + } + + } + + return entries; + } + + public async Task<(List, List)> QueryResourceAndResourceSetsAsync(string queryText, string mimeType) + + { + ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; + var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); + + var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); + requestMessage.SetHeader("Accept", mimeType); + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(200, responseMessage.StatusCode); + + var resourceEntries = new List(); + var resourceSetEntries = new List(); + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) + { + var reader = await messageReader.CreateODataResourceSetReaderAsync(); + while (await reader.ReadAsync()) + { + if (reader.State == ODataReaderState.ResourceEnd && reader.Item is ODataResource odataResource) + { + resourceEntries.Add(odataResource); + } + else if (reader.State == ODataReaderState.ResourceSetEnd && reader.Item is ODataResourceSet odataResourceSet) + { + resourceSetEntries.Add(odataResourceSet); + } + } + Assert.Equal(ODataReaderState.Completed, reader.State); + } + + } + + return (resourceEntries, resourceSetEntries); + } + + /// + /// Queries an OData resource set asynchronously based on the provided query text and MIME type. + /// + /// The query text to append to the base URI. + /// The MIME type to set in the request header. + /// A task that represents the asynchronous operation. The task result contains an if found; otherwise, null. + public async Task QueryODataResourceSetAsync(string queryText, string mimeType) + { + ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; + var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); + + var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); + requestMessage.SetHeader("Accept", mimeType); + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(200, responseMessage.StatusCode); + + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) + { + var reader = await messageReader.CreateODataResourceReaderAsync(); + while (await reader.ReadAsync()) + { + if (reader.State == ODataReaderState.ResourceSetEnd && reader.Item is ODataResourceSet oDataResourceSet) + { + return oDataResourceSet; + } + } + } + } + + return null; + } + + /// + /// Queries a property asynchronously based on the provided request URI and MIME type. + /// + /// The request URI to append to the base URI. + /// The MIME type to set in the request header. + /// A task that represents the asynchronous operation. The task result contains an if found; otherwise, null. + public async Task QueryPropertyAsync(string requestUri, string mimeType) + { + var readerSettings = new ODataMessageReaderSettings() { BaseUri = BaseUri }; + + var uri = new Uri(BaseUri.AbsoluteUri + requestUri, UriKind.Absolute); + var requestMessage = new TestHttpClientRequestMessage(uri, Client); + + requestMessage.SetHeader("Accept", mimeType); + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(200, responseMessage.StatusCode); + + ODataProperty? property = null; + + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) + { + property = messageReader.ReadProperty(); + } + } + + return property; + } + + public async Task QueryPropertyValueInStringAsync(string requestUri) + { + var readerSettings = new ODataMessageReaderSettings() { BaseUri = BaseUri }; + + var uri = new Uri(BaseUri.AbsoluteUri + requestUri, UriKind.Absolute); + var requestMessage = new TestHttpClientRequestMessage(uri, Client); + + requestMessage.SetHeader("Accept", "*/*"); + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(200, responseMessage.StatusCode); + + using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) + { + return messageReader.ReadValue(EdmCoreModel.Instance.GetString(false)); + } + } +} diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs new file mode 100644 index 0000000000..ff06edbbed --- /dev/null +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs @@ -0,0 +1,134 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; + +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; + +public class SingletonTestsController : ODataController +{ + private static DefaultDataSource _dataSource; + + [EnableQuery] + [HttpGet("odata/VipCustomer")] + public IActionResult GetVipCustomer() + { + var result = _dataSource.VipCustomer; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/PersonID")] + public IActionResult GetVipCustomerPersonID() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.PersonID); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/Orders")] + public IActionResult GetVipCustomerOrders() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.Orders); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/Orders({key})/OrderDate")] + public IActionResult GetVipCustomerOrderOrderDate([FromRoute] int key) + { + var result = _dataSource.VipCustomer?.Orders?.SingleOrDefault(a => a.OrderID == key); + + return Ok(result?.OrderDate); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/HomeAddress")] + public IActionResult GetVipCustomerHomeAddress() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.HomeAddress); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/HomeAddress/City")] + public IActionResult GetVipCustomerHomeAddressCity() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.HomeAddress?.City); + } + + [EnableQuery] + [HttpGet("odata/Company")] + public IActionResult GetCompany() + { + var result = _dataSource.Company; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/Company/CompanyCategory")] + public IActionResult GetCompanyCompanyCategory() + { + var result = _dataSource.Company; + + return Ok(result?.CompanyCategory); + } + + [EnableQuery] + [HttpGet("odata/Boss")] + public IActionResult GetBoss() + { + var result = _dataSource.Boss; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer")] + public IActionResult GetBossFromDerivedType() + { + var result = _dataSource.Boss; + + if (result is not Customer customer) + { + return NotFound(); + } + + return Ok(customer); + } + + [EnableQuery] + [HttpGet("odata/Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer/City")] + public IActionResult GetBossCityFromDerivedType() + { + var result = _dataSource.Boss; + + if (result is not Customer customer) + { + return NotFound(); + } + + return Ok(customer.City); + } + + [HttpPost("odata/singletontests/Default.ResetDefaultDataSource")] + public IActionResult ResetDefaultDataSource() + { + _dataSource = DefaultDataSource.CreateInstance(); + + return Ok(); + } +} diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs new file mode 100644 index 0000000000..0544628660 --- /dev/null +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs @@ -0,0 +1,474 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.AspNetCore.OData; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Client.E2E.TestCommon; +using Microsoft.OData.Client.E2E.TestCommon.Common; +using Microsoft.OData.Client.E2E.TestCommon.Helpers; +using Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Default; +using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; +using Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; +using Microsoft.OData.Edm; +using Xunit; + +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Tests; + +public class SingletonQueryTests : EndToEndTestBase +{ + private readonly Uri _baseUri; + private readonly Container _context; + private readonly IEdmModel _model; + + public class TestsStartup : TestStartupBase + { + public override void ConfigureServices(IServiceCollection services) + { + services.ConfigureControllers(typeof(SingletonTestsController), typeof(MetadataController)); + + services.AddControllers().AddOData(opt => + opt.EnableQueryFeatures().AddRouteComponents("odata", DefaultEdmModel.GetEdmModel())); + } + } + + public SingletonQueryTests(TestWebApplicationFactory fixture) : base(fixture) + { + if (Client.BaseAddress == null) + { + throw new ArgumentNullException(nameof(Client.BaseAddress), "Base address cannot be null"); + } + + _baseUri = new Uri(Client.BaseAddress, "odata/"); + + _context = new Container(_baseUri) + { + HttpClientFactory = HttpClientFactory + }; + + _model = DefaultEdmModel.GetEdmModel(); + ResetDefaultDataSource(); + } + + public static IEnumerable MimeTypesData + { + get + { + yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata }; + yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata }; + yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterNoMetadata }; + } + } + + #region Query singleton entity + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingleton(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); + var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(entry); + + var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var lastNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "LastName") as ODataProperty; + var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + var emailsProperty = entry.Properties.SingleOrDefault(p => p.Name == "Emails") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(lastNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(1, personIDProperty.Value); + Assert.Equal("Bob", firstNameProperty.Value); + Assert.Equal("Cat", lastNameProperty.Value); + Assert.Equal("London", cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonWhichIsOpenType(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceEntriesAsync("Company", mimeType); + var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Company")); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(entry); + + var companyIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "CompanyID") as ODataProperty; + var companyCategoryProperty = entry.Properties.SingleOrDefault(p => p.Name == "CompanyCategory") as ODataProperty; + var nameProperty = entry.Properties.SingleOrDefault(p => p.Name == "Name") as ODataProperty; + Assert.NotNull(companyIDProperty); + Assert.NotNull(companyCategoryProperty); + Assert.NotNull(nameProperty); + + Assert.Equal(0, companyIDProperty.Value); + Assert.Equal("IT", companyCategoryProperty.Value.ToString()); + Assert.Equal("MS", nameProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QueryDerivedSingletonWithTypeCast(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceEntriesAsync("Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer", mimeType); + var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(entry); + + var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var lastNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "LastName") as ODataProperty; + var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(lastNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(2, personIDProperty.Value); + Assert.Equal("Jill", firstNameProperty.Value); + Assert.Equal("Jones", lastNameProperty.Value); + Assert.Equal("Sydney", cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QueryDerivedSingletonWithoutTypeCast(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceEntriesAsync("Boss", mimeType); + var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(entry); + + var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(2, personIDProperty.Value); + Assert.Equal("Jill", firstNameProperty.Value); + Assert.Equal("Sydney", cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonWithExpand(string mimeType) + { + // Arrange & Act + var resources = await this.TestsHelper.QueryResourceEntriesAsync("VipCustomer?$expand=Orders", mimeType); + var entries = resources.Where(r => r != null && r.Id != null).ToList(); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + var orders = entries.FindAll(e => e.Id.AbsoluteUri.Contains("Orders")); + Assert.Equal(2, orders.Count); + + var customer = entries.SingleOrDefault(e => e.Id.AbsoluteUri.Contains("VipCustomer")); + Assert.NotNull(customer); + + var personIDProperty = customer.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(1, personIDProperty.Value); + Assert.Equal("Bob", firstNameProperty.Value); + Assert.Equal("London", cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonWithSelect(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceEntriesAsync("VipCustomer?$select=PersonID,FirstName", mimeType); + var customer = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(customer); + Assert.Equal(2, customer.Properties.Count()); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QueryDerivedSingletonWithTypeCastAndSelect(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceEntriesAsync("Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer?$select=City", mimeType); + var customer = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(customer); + Assert.Single(customer.Properties); + var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(cityProperty); + Assert.Equal("Sydney", cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonWithSelectUnderExpand(string mimeType) + { + // Arrange & Act + var resources = await this.TestsHelper.QueryResourceEntriesAsync("VipCustomer?$expand=Orders($select=OrderID,OrderDate)", mimeType); + var entries = resources.Where(r => r != null && r.Id != null).ToList(); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + var orders = entries.FindAll(e => e != null && e.Id.AbsoluteUri.Contains("Orders")); + Assert.Equal(2, orders.Count); + + foreach (var order in orders) + { + Assert.Equal(2, order.Properties.Count()); + Assert.Contains(order.Properties, p => p.Name == "OrderID"); + Assert.Contains(order.Properties, p => p.Name == "OrderDate"); + } + + var customer = entries.SingleOrDefault(e => e != null && e.Id.AbsoluteUri.Contains("VipCustomer")); + Assert.NotNull(customer); + + var personIDProperty = customer.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(1, personIDProperty.Value); + Assert.Equal("Bob", firstNameProperty.Value); + Assert.Equal("London", cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonWithMiscQueryOptions(string mimeType) + { + // Arrange & Act + var resources = await this.TestsHelper.QueryResourceEntriesAsync("VipCustomer?$select=FirstName,HomeAddress&$expand=Orders", mimeType); + var entries = resources.Where(r => r != null && r.Id != null).ToList(); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + var orders = entries.FindAll(e => e.Id.AbsoluteUri.Contains("Orders")); + Assert.Equal(2, orders.Count); + + var customer = entries.SingleOrDefault(e => e.Id.AbsoluteUri.Contains("VipCustomer")); + Assert.NotNull(customer); + Assert.Single(customer.Properties); + + var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + Assert.NotNull(firstNameProperty); + Assert.Equal("Bob", firstNameProperty.Value); + + var homeAddress = resources.SingleOrDefault(r => r != null && r.TypeName.EndsWith("Address")); + Assert.NotNull(homeAddress); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task SelectDerivedPropertyWithoutTypeCastShouldFail(string mimeType) + { + await this.BadRequestOrNotFoundAsync("Boss?$select=City", mimeType, /* Bad Request */ 400); + } + + #endregion + + #region Query singleton property + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonProperty(string mimeType) + { + // Arrange & Act + var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/PersonID", mimeType); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(property); + Assert.Equal(1, property.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonPropertyUnderComplexProperty(string mimeType) + { + // Arrange & Act + var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/HomeAddress/City", mimeType); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(property); + Assert.Equal("London", property.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonEnumProperty(string mimeType) + { + // Arrange & Act + var property = await this.TestsHelper.QueryPropertyAsync("Company/CompanyCategory", mimeType); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(property); + Assert.Equal("IT", property.Value.ToString()); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonNavigationProperty(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceSetsAsync("VipCustomer/Orders", mimeType); + var entry = entries.FirstOrDefault(); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(entry); + var orderIdProperty = entry.Properties.Single(p => p.Name == "OrderID") as ODataProperty; + Assert.NotNull(orderIdProperty); + Assert.Equal(7, orderIdProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonPropertyUnderNavigationProperty(string mimeType) + { + // Arrange & Act + var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/Orders(8)/OrderDate", mimeType); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(property); + Assert.Equal(new DateTimeOffset(2011, 3, 4, 16, 3, 57, TimeSpan.FromHours(-8)), property.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QueryDerivedSingletonPropertyWithTypeCast(string mimeType) + { + // Arrange & Act + var property = await this.TestsHelper.QueryPropertyAsync("Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer/City", mimeType); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(property); + Assert.Equal("Sydney", property.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QuerySingletonNavigationPropertyWithFilter(string mimeType) + { + // Arrange & Act + var entries = await this.TestsHelper.QueryResourceSetsAsync("VipCustomer/Orders?$filter=OrderID eq 8", mimeType); + var entry = entries.FirstOrDefault(); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + Assert.NotNull(entry); + var orderDateProperty = entry.Properties.Single(p => p.Name == "OrderDate") as ODataProperty; + Assert.NotNull(orderDateProperty); + Assert.Equal(new DateTimeOffset(2011, 3, 4, 16, 3, 57, TimeSpan.FromHours(-8)), orderDateProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task QueryDerivedPropertyWithoutTypeCastShouldFail(string mimeType) + { + // Arrange & Act & Assert + await this.BadRequestOrNotFoundAsync("Boss/City", mimeType, /* Not Found */ 404); + } + + #endregion + + #region Private methods + + private async Task BadRequestOrNotFoundAsync(string requestUri, string mimeType, int errorCode) + { + ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri }; + var requestUrl = new Uri(_baseUri.AbsoluteUri + requestUri, UriKind.Absolute); + + var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); + requestMessage.SetHeader("Accept", mimeType); + if (mimeType == MimeTypes.ApplicationAtomXml) + { + requestMessage.SetHeader("Accept", "text/html, application/xhtml+xml, */*"); + } + + var responseMessage = await requestMessage.GetResponseAsync(); + + Assert.Equal(errorCode, responseMessage.StatusCode); + } + + private ODataMessageReaderTestsHelper TestsHelper + { + get + { + return new ODataMessageReaderTestsHelper(_baseUri, _model, Client); + } + } + + private void ResetDefaultDataSource() + { + var actionUri = new Uri(_baseUri + "singletontests/Default.ResetDefaultDataSource", UriKind.Absolute); + _context.Execute(actionUri, "POST"); + } + + #endregion +} From dcf07fa7b606f7731b22e4b7963ec716d45ad822 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Thu, 9 Jan 2025 23:20:35 +0300 Subject: [PATCH 2/5] Added Singleton client tests --- .../Common/ODataAnnotatableExtensions.cs | 51 ++ .../Helpers/ODataValueAssertEqualHelper.cs | 41 +- .../Common/Client/Default/DefaultContainer.cs | 20 +- .../Client/Default/DefaultServiceCsdl.xml | 7 +- .../Common/Server/Default/DefaultEdmModel.cs | 8 +- .../Server/SingletonClientTestsController.cs | 610 ++++++++++++++++++ .../Server/SingletonTestsController.cs | 96 +++ .../Tests/SingletonClientTests.cs | 512 +++++++++++++++ .../Tests/SingletonQueryTests.cs | 2 + .../Tests/SingletonUpdateTests.cs | 248 +++++++ 10 files changed, 1582 insertions(+), 13 deletions(-) create mode 100644 test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Common/ODataAnnotatableExtensions.cs create mode 100644 test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonClientTestsController.cs create mode 100644 test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonClientTests.cs create mode 100644 test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs diff --git a/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Common/ODataAnnotatableExtensions.cs b/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Common/ODataAnnotatableExtensions.cs new file mode 100644 index 0000000000..77a31202a1 --- /dev/null +++ b/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Common/ODataAnnotatableExtensions.cs @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Concurrent; +using System.Diagnostics; + +namespace Microsoft.OData.Client.E2E.TestCommon.Common; + +public static class ODataAnnotatableExtensions +{ + public static void SetAnnotation(this ODataAnnotatable annotatable, T annotation) + where T : class + { + Debug.Assert(annotatable != null, "annotatable != null"); + Debug.Assert(annotation != null, "annotation != null"); + + InternalDictionary.SetAnnotation(annotatable, annotation); + } + + public static T GetAnnotation(this ODataAnnotatable annotatable) + where T : class + { + Debug.Assert(annotatable != null, "annotatable != null"); + + return InternalDictionary.GetAnnotation(annotatable); + } + + private static class InternalDictionary where T : class + { + private static readonly ConcurrentDictionary Dictionary = + new ConcurrentDictionary(); + + public static void SetAnnotation(ODataAnnotatable annotatable, T annotation) + { + Dictionary[annotatable] = annotation; + } + + public static T GetAnnotation(ODataAnnotatable annotatable) + { + if (Dictionary.TryGetValue(annotatable, out T? annotation)) + { + return annotation; + } + + return default(T); + } + } +} diff --git a/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataValueAssertEqualHelper.cs b/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataValueAssertEqualHelper.cs index b2043f7b3d..37f3618b72 100644 --- a/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataValueAssertEqualHelper.cs +++ b/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataValueAssertEqualHelper.cs @@ -14,16 +14,16 @@ public static class ODataValueAssertEqualHelper public static void AssertODataValueEqual(ODataValue expected, ODataValue actual) { - ODataPrimitiveValue expectedPrimitiveValue = expected as ODataPrimitiveValue; - ODataPrimitiveValue actualPrimitiveValue = actual as ODataPrimitiveValue; + var expectedPrimitiveValue = expected as ODataPrimitiveValue; + var actualPrimitiveValue = actual as ODataPrimitiveValue; if (expectedPrimitiveValue != null && actualPrimitiveValue != null) { AssertODataPrimitiveValueEqual(expectedPrimitiveValue, actualPrimitiveValue); } else { - ODataEnumValue expectedEnumValue = expected as ODataEnumValue; - ODataEnumValue actualEnumValue = actual as ODataEnumValue; + var expectedEnumValue = expected as ODataEnumValue; + var actualEnumValue = actual as ODataEnumValue; if (expectedEnumValue != null && actualEnumValue != null) { AssertODataEnumValueEqual(expectedEnumValue, actualEnumValue); @@ -42,10 +42,12 @@ private static void AssertODataCollectionValueEqual(ODataCollectionValue expecte Assert.NotNull(expectedCollectionValue); Assert.NotNull(actualCollectionValue); Assert.Equal(expectedCollectionValue.TypeName, actualCollectionValue.TypeName); + var expectedItemsArray = expectedCollectionValue.Items.OfType().ToArray(); var actualItemsArray = actualCollectionValue.Items.OfType().ToArray(); Assert.Equal(expectedItemsArray.Length, actualItemsArray.Length); + for (int i = 0; i < expectedItemsArray.Length; i++) { var expectedOdataValue = expectedItemsArray[i] as ODataValue; @@ -61,7 +63,7 @@ private static void AssertODataCollectionValueEqual(ODataCollectionValue expecte } } - public static void AssertODataPropertiesEqual(IEnumerable expectedProperties, IEnumerable actualProperties) + public static void AssertODataPropertiesEqual(IEnumerable expectedProperties, IEnumerable actualProperties) { if (expectedProperties == null && actualProperties == null) { @@ -70,21 +72,43 @@ public static void AssertODataPropertiesEqual(IEnumerable expecte Assert.NotNull(expectedProperties); Assert.NotNull(actualProperties); + var expectedPropertyArray = expectedProperties.ToArray(); var actualPropertyArray = actualProperties.ToArray(); Assert.Equal(expectedPropertyArray.Length, actualPropertyArray.Length); + for (int i = 0; i < expectedPropertyArray.Length; i++) { AssertODataPropertyEqual(expectedPropertyArray[i], actualPropertyArray[i]); } } - public static void AssertODataPropertyEqual(ODataProperty expectedOdataProperty, ODataProperty actualOdataProperty) + public static void AssertODataPropertyAndResourceEqual(ODataResource expectedOdataProperty, ODataResource actualOdataProperty) { Assert.NotNull(expectedOdataProperty); Assert.NotNull(actualOdataProperty); - Assert.Equal(expectedOdataProperty.Name, actualOdataProperty.Name); - AssertODataValueEqual(ToODataValue(expectedOdataProperty.Value), ToODataValue(actualOdataProperty.Value)); + AssertODataValueAndResourceEqual(expectedOdataProperty, actualOdataProperty); + } + + public static void AssertODataValueAndResourceEqual(ODataResource expected, ODataResource actual) + { + Assert.Equal(expected.TypeName, actual.TypeName); + AssertODataPropertiesEqual(expected.Properties, actual.Properties); + } + + public static void AssertODataPropertyEqual(ODataPropertyInfo expectedPropertyInfo, ODataPropertyInfo actualPropertyInfo) + { + Assert.NotNull(expectedPropertyInfo); + Assert.NotNull(actualPropertyInfo); + Assert.Equal(expectedPropertyInfo.Name, actualPropertyInfo.Name); + + var expectedProperty = expectedPropertyInfo as ODataProperty; + var actualProperty = actualPropertyInfo as ODataProperty; + + Assert.NotNull(expectedProperty); + Assert.NotNull(actualProperty); + + AssertODataValueEqual(ToODataValue(expectedProperty.Value), ToODataValue(actualProperty.Value)); } private static ODataValue ToODataValue(object value) @@ -119,5 +143,4 @@ private static void AssertODataEnumValueEqual(ODataEnumValue expectedEnumValue, } #endregion Util methods to AssertEqual ODataValues - } diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultContainer.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultContainer.cs index 0389db9d40..a297bdba77 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultContainer.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultContainer.cs @@ -7305,7 +7305,11 @@ private abstract class GeneratedEdmModel try { var assembly = global::System.Reflection.Assembly.GetExecutingAssembly(); - var resourcePath = global::System.Linq.Enumerable.Single(assembly.GetManifestResourceNames(), str => str.EndsWith(filePath)); + // If multiple resource names end with the file name, select the shortest one. + var resourcePath = global::System.Linq.Enumerable.First( + global::System.Linq.Enumerable.OrderBy( + global::System.Linq.Enumerable.Where(assembly.GetManifestResourceNames(), name => name.EndsWith(filePath)), + filteredName => filteredName.Length)); global::System.IO.Stream stream = assembly.GetManifestResourceStream(resourcePath); return global::System.Xml.XmlReader.Create(new global::System.IO.StreamReader(stream)); } @@ -7402,6 +7406,20 @@ private abstract class GeneratedEdmModel /// public static class ExtensionMethods { + /// + /// There are no comments for GetEmployeesCount in the schema. + /// + [global::Microsoft.OData.Client.OriginalNameAttribute("GetEmployeesCount")] + public static global::Microsoft.OData.Client.DataServiceQuerySingle GetEmployeesCount(this global::Microsoft.OData.Client.DataServiceQuerySingle _source) + { + if (!_source.IsComposable) + { + throw new global::System.NotSupportedException("The previous function is not composable."); + } + + return _source.CreateFunctionQuerySingle("Default.GetEmployeesCount", false); + } + /// /// There are no comments for GetProductDetails in the schema. /// diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultServiceCsdl.xml b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultServiceCsdl.xml index e01225d970..7180d19f66 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultServiceCsdl.xml +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Client/Default/DefaultServiceCsdl.xml @@ -355,6 +355,10 @@ + + + + @@ -492,7 +496,8 @@ - + + diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Server/Default/DefaultEdmModel.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Server/Default/DefaultEdmModel.cs index 87a6e805f2..1e825e4387 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Server/Default/DefaultEdmModel.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/Common/Server/Default/DefaultEdmModel.cs @@ -29,8 +29,8 @@ public static IEdmModel GetEdmModel() builder.EntitySet("OrderDetails"); builder.EntitySet("Departments"); builder.Singleton("Company"); - builder.Singleton("PublicCompany"); - builder.Singleton("LabourUnion"); + builder.Singleton("PublicCompany").HasSingletonBinding((PublicCompany p) => p.LabourUnion, "LabourUnion"); + // builder.Singleton("LabourUnion"); builder.EntitySet("Accounts"); builder.EntitySet("Orders"); builder.EntitySet("PaymentInstruments"); @@ -95,6 +95,10 @@ public static IEdmModel GetEdmModel() builder.Action("ResetDefaultDataSource"); + builder.EntityType() + .Function("GetEmployeesCount") + .Returns(); + builder.EntityType() .Function("GetProductDetails") .ReturnsCollectionViaEntitySetPath("bindingParameter/Details") diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonClientTestsController.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonClientTestsController.cs new file mode 100644 index 0000000000..b9322ca5e4 --- /dev/null +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonClientTestsController.cs @@ -0,0 +1,610 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Deltas; +using Microsoft.AspNetCore.OData.Formatter; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; + +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; + +public class SingletonClientTestsController : ODataController +{ + private static DefaultDataSource _dataSource; + + + #region odata/VipCustomer + + [EnableQuery] + [HttpGet("odata/VipCustomer")] + public IActionResult GetVipCustomer() + { + var result = _dataSource.VipCustomer; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/PersonID")] + public IActionResult GetVipCustomerPersonID() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.PersonID); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/Orders")] + public IActionResult GetVipCustomerOrders() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.Orders); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/Orders({key})/OrderDate")] + public IActionResult GetVipCustomerOrderOrderDate([FromRoute] int key) + { + var result = _dataSource.VipCustomer?.Orders?.SingleOrDefault(a => a.OrderID == key); + + return Ok(result?.OrderDate); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/HomeAddress")] + public IActionResult GetVipCustomerHomeAddress() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.HomeAddress); + } + + [EnableQuery] + [HttpGet("odata/VipCustomer/HomeAddress/City")] + public IActionResult GetVipCustomerHomeAddressCity() + { + var result = _dataSource.VipCustomer; + + return Ok(result?.HomeAddress?.City); + } + + [HttpPatch("odata/VipCustomer")] + public IActionResult UpdateVipCustomer([FromBody] Delta delta) + { + var customer = _dataSource.VipCustomer; + if (customer == null) + { + return NotFound(); + } + + var updatedResult = delta.Patch(customer); + return Updated(updatedResult); + } + + #endregion + + #region odata/Company + + [EnableQuery] + [HttpGet("odata/Company")] + public IActionResult GetCompany() + { + var result = _dataSource.Company; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/Company/Name")] + public IActionResult GetCompanyName() + { + var result = _dataSource.Company; + + return Ok(result?.Name); + } + + [EnableQuery] + [HttpGet("odata/Company/CompanyCategory")] + public IActionResult GetCompanyCompanyCategory() + { + var result = _dataSource.Company; + + return Ok(result?.CompanyCategory); + } + + [EnableQuery] + [HttpGet("odata/Company/VipCustomer")] + public IActionResult GetCompanyVipCustomer() + { + var result = _dataSource.Company; + + return Ok(result?.VipCustomer); + } + + [EnableQuery] + [HttpGet("odata/Company/Departments")] + public IActionResult GetCompanyDepartments() + { + var result = _dataSource.Company; + + return Ok(result?.Departments); + } + + [EnableQuery] + [HttpGet("odata/Company/Revenue")] + public IActionResult GetRevenue() + { + var result = _dataSource.Company; + + return Ok(result?.Revenue); + } + + [EnableQuery] + [HttpGet("odata/Company/CoreDepartment")] + public IActionResult GetCompanyCoreDepartment() + { + var result = _dataSource.Company; + + return Ok(result?.CoreDepartment); + } + + [EnableQuery] + [HttpGet("odata/Company/Address/City")] + public IActionResult GetCompanyAddressCity() + { + var result = _dataSource.Company; + + return Ok(result?.Address?.City); + } + + [EnableQuery] + [HttpGet("odata/Company/Default.GetEmployeesCount")] + public IActionResult GetEmployeesCount() + { + var result = _dataSource.Company; + + if (result == null) + { + return NotFound(); + } + + return Ok(result.Employees?.Count); + } + + [EnableQuery] + [HttpPost("odata/Company/Default.IncreaseRevenue")] + public IActionResult IncreaseRevenue([FromODataBody] int IncreaseValue) + { + var result = _dataSource.Company; + + if (result == null) + { + return NotFound(); + } + + result.Revenue += IncreaseValue; + + return Ok(result.Revenue); + } + + [HttpPost("odata/Company/Departments/$ref")] + public IActionResult AddDepartmentRefToCompany([FromBody] Uri departmentUri) + { + if (departmentUri == null) + { + return BadRequest(); + } + + // Extract the department ID from the URI + var lastSegment = departmentUri.Segments.Last(); + var departmentId = int.Parse(Regex.Match(lastSegment, @"\d+").Value); + + // Find the department by ID + var department = _dataSource.Departments?.SingleOrDefault(d => d.DepartmentID == departmentId); + if (department == null) + { + return NotFound(); + } + + // Add the department reference to the company + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + company.Departments ??= []; + company.Departments.Add(department); + + return Ok(department); + } + + [HttpPut("odata/company")] + public IActionResult UpdateCompany([FromBody] Company company) + { + var companyToUpdate = _dataSource.Company; + if (companyToUpdate == null) + { + return NotFound(); + } + + companyToUpdate.CompanyID = company.CompanyID == 0 ? companyToUpdate.CompanyID : company.CompanyID; + companyToUpdate.Address = company.Address ?? companyToUpdate.Address; + companyToUpdate.CompanyCategory = company.CompanyCategory; + companyToUpdate.Name = company.Name ?? companyToUpdate.Name; + companyToUpdate.Employees = company.Employees ?? companyToUpdate.Employees; + companyToUpdate.Revenue = company.Revenue; + companyToUpdate.CoreDepartment = company.CoreDepartment ?? companyToUpdate.CoreDepartment; + companyToUpdate.VipCustomer = company.VipCustomer ?? companyToUpdate.VipCustomer; + companyToUpdate.Departments = company.Departments ?? companyToUpdate.Departments; + + return Updated(companyToUpdate); + } + + [HttpPatch("odata/company")] + public IActionResult PatchCompany([FromBody] Delta delta) + { + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + var updatedResult = delta.Patch(company); + return Updated(updatedResult); + } + + [HttpPut("odata/Company/CoreDepartment/$ref")] + public IActionResult UpdateCompanyCoreDepartmentRef([FromBody] Uri departmentUri) + { + if (departmentUri == null) + { + return BadRequest(); + } + + // Extract the department ID from the URI + var lastSegment = departmentUri.Segments.Last(); + var departmentId = int.Parse(Regex.Match(lastSegment, @"\d+").Value); + + // Find the department by ID + var department = _dataSource.Departments?.SingleOrDefault(d => d.DepartmentID == departmentId); + if (department == null) + { + return NotFound(); + } + + // Update the core department reference in the company + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + company.CoreDepartment = department; + + return NoContent(); + } + + [HttpPut("odata/Company/VipCustomer/$ref")] + public IActionResult UpdateCompanyVipCustomerRef([FromBody] Uri vipCustomerUri) + { + var vipCustomer = _dataSource.VipCustomer; + if (vipCustomer == null) + { + return NotFound(); + } + + // Update the vipCustomer reference in the company + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + company.VipCustomer = vipCustomer; + + return NoContent(); + } + + [HttpDelete("odata/Company/Departments/$ref")] + public IActionResult DeleteDepartmentRefFromCompany([FromODataUri] string id) + { + id ??= Request.Query["$id"].ToString(); + + var uriId = new Uri(id); + + // Extract the department ID from the URI + var lastSegment = uriId.Segments.Last(); + var departmentId = int.Parse(Regex.Match(lastSegment, @"\d+").Value); + + // Find the department by ID + var department = _dataSource.Departments?.SingleOrDefault(d => d.DepartmentID == departmentId); + if (department == null) + { + return NotFound(); + } + + // Remove the department reference from the company + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + company.Departments?.Remove(department); + + return NoContent(); + } + + [HttpDelete("odata/Company/VipCustomer/$ref")] + public IActionResult DeleteVipCustomerRefFromCompany() + { + // Remove the VipCustomer reference from the company + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + company.VipCustomer = null; + + return NoContent(); + } + + #endregion + + #region odata/Boss + + [EnableQuery] + [HttpGet("odata/Boss")] + public IActionResult GetBoss() + { + var result = _dataSource.Boss; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer")] + public IActionResult GetBossFromDerivedType() + { + var result = _dataSource.Boss; + + if (result is not Customer customer) + { + return NotFound(); + } + + return Ok(customer); + } + + [EnableQuery] + [HttpGet("odata/Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer/City")] + public IActionResult GetBossCityFromDerivedType() + { + var result = _dataSource.Boss; + + if (result is not Customer customer) + { + return NotFound(); + } + + return Ok(customer.City); + } + + #endregion + + #region odata/Departments + + [EnableQuery] + [HttpGet("odata/Departments")] + public IActionResult GetDepartments() + { + var result = _dataSource.Departments; + + return Ok(result); + } + + [HttpPut("odata/Departments({key})/Company/$ref")] + public IActionResult UpdateDepartmentCompanyRef([FromRoute] int key, [FromBody] Uri companyUri) + { + if (companyUri == null) + { + return BadRequest(); + } + + // Find the company by ID + var company = _dataSource.Company; + if (company == null) + { + return NotFound(); + } + + // Find the department by ID + var department = _dataSource.Departments?.SingleOrDefault(d => d.DepartmentID == key); + if (department == null) + { + return NotFound(); + } + + // Update the company reference in the department + department.Company = company; + + return NoContent(); + } + + [HttpPost("odata/Departments")] + public IActionResult CreateDepartment([FromBody] Department department) + { + if (department == null) + { + return BadRequest(); + } + + _dataSource.Departments?.Add(department); + return Created(department); + } + + #endregion + + #region odata/PublicCompany + + [EnableQuery] + [HttpGet("odata/PublicCompany")] + public IActionResult GetPublicCompany() + { + var result = _dataSource.PublicCompany; + + return Ok(result); + } + + [EnableQuery] + [HttpGet("odata/PublicCompany/Name")] + public IActionResult GetPublicCompanyName() + { + var result = _dataSource.PublicCompany; + + return Ok(result?.Name); + } + + [EnableQuery] + [HttpGet("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/StockExchange")] + public IActionResult GetPublicCompanyStockExchange() + { + var result = _dataSource.PublicCompany; + + return Ok(result?.StockExchange); + } + + [EnableQuery] + [HttpGet("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/Assets")] + public IActionResult GetPublicCompanyAssets() + { + var result = _dataSource.PublicCompany; + + return Ok(result?.Assets); + } + + [EnableQuery] + [HttpGet("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/Club")] + public IActionResult GetPublicCompanyClub() + { + var result = _dataSource.PublicCompany; + + return Ok(result?.Club); + } + + [EnableQuery] + [HttpGet("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/LabourUnion")] + public IActionResult GetPublicCompanyLabourUnion() + { + var result = _dataSource.PublicCompany; + + return Ok(result?.LabourUnion); + } + + [HttpPost("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/Assets")] + public IActionResult AddAssetToPublicCompanyAssets([FromBody] Asset asset) + { + var result = _dataSource.PublicCompany; + if (result == null) + { + return NotFound(); + } + + result.Assets ??= []; + result.Assets.Add(asset); + + return Created(asset); + } + + [HttpPut("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany")] + public IActionResult UpdatePublicCompany([FromBody] PublicCompany company) + { + var companyToUpdate = _dataSource.PublicCompany; + + if (companyToUpdate == null) + { + return NotFound(); + } + + companyToUpdate.CompanyID = company.CompanyID == 0 ? companyToUpdate.CompanyID : company.CompanyID; + companyToUpdate.Address = company.Address ?? companyToUpdate.Address; + companyToUpdate.CompanyCategory = company.CompanyCategory; + companyToUpdate.Name = company.Name ?? companyToUpdate.Name; + companyToUpdate.Employees = company.Employees ?? companyToUpdate.Employees; + companyToUpdate.Revenue = company.Revenue; + companyToUpdate.CoreDepartment = company.CoreDepartment ?? companyToUpdate.CoreDepartment; + companyToUpdate.VipCustomer = company.VipCustomer ?? companyToUpdate.VipCustomer; + companyToUpdate.Departments = company.Departments ?? companyToUpdate.Departments; + companyToUpdate.StockExchange = company.StockExchange ?? companyToUpdate.StockExchange; + companyToUpdate.Assets = company.Assets ?? companyToUpdate.Assets; + companyToUpdate.Club = company.Club ?? companyToUpdate.Club; + companyToUpdate.LabourUnion = company.LabourUnion ?? companyToUpdate.LabourUnion; + + return Updated(companyToUpdate); + } + + [HttpPatch("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/Club")] + public IActionResult PatchPublicCompanyClub([FromBody] Delta delta) + { + var club = _dataSource.PublicCompany?.Club; + if (club == null) + { + return NotFound(); + } + + var updatedResult = delta.Patch(club); + return Updated(updatedResult); + } + + [HttpPatch("odata/LabourUnion")] + public IActionResult PatchPublicCompanyLabourUnion([FromBody] Delta delta) + { + var labourUnion = _dataSource.LabourUnion; + if (labourUnion == null) + { + return NotFound(); + } + + var updatedResult = delta.Patch(labourUnion); + return Updated(updatedResult); + } + + [HttpDelete("odata/PublicCompany/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.PublicCompany/Assets({key})")] + public IActionResult RemoveAssetFromPublicCompanyAssets([FromRoute] int key) + { + var result = _dataSource.PublicCompany; + if (result == null) + { + return NotFound(); + } + + var asset = result.Assets?.SingleOrDefault(a => a.AssetID == key); + if (asset == null) + { + return NotFound(); + } + + result.Assets?.Remove(asset); + + return NoContent(); + } + + #endregion + + [HttpPost("odata/singletonclienttests/Default.ResetDefaultDataSource")] + public IActionResult ResetDefaultDataSource() + { + _dataSource = DefaultDataSource.CreateInstance(); + + return Ok(); + } +} diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs index ff06edbbed..447c68e86b 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs @@ -4,7 +4,10 @@ // //--------------------------------------------------------------------- +using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Deltas; +using Microsoft.AspNetCore.OData.Formatter; using Microsoft.AspNetCore.OData.Query; using Microsoft.AspNetCore.OData.Routing.Controllers; using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; @@ -15,6 +18,9 @@ public class SingletonTestsController : ODataController { private static DefaultDataSource _dataSource; + + #region odata/VipCustomer + [EnableQuery] [HttpGet("odata/VipCustomer")] public IActionResult GetVipCustomer() @@ -69,6 +75,23 @@ public IActionResult GetVipCustomerHomeAddressCity() return Ok(result?.HomeAddress?.City); } + [HttpPatch("odata/VipCustomer")] + public IActionResult UpdateVipCustomer([FromBody] Delta delta) + { + var customer = _dataSource.VipCustomer; + if (customer == null) + { + return NotFound(); + } + + var updatedResult = delta.Patch(customer); + return Updated(updatedResult); + } + + #endregion + + #region odata/Company + [EnableQuery] [HttpGet("odata/Company")] public IActionResult GetCompany() @@ -78,6 +101,15 @@ public IActionResult GetCompany() return Ok(result); } + [EnableQuery] + [HttpGet("odata/Company/Name")] + public IActionResult GetCompanyName() + { + var result = _dataSource.Company; + + return Ok(result?.Name); + } + [EnableQuery] [HttpGet("odata/Company/CompanyCategory")] public IActionResult GetCompanyCompanyCategory() @@ -87,6 +119,55 @@ public IActionResult GetCompanyCompanyCategory() return Ok(result?.CompanyCategory); } + [EnableQuery] + [HttpGet("odata/Company/VipCustomer")] + public IActionResult GetCompanyVipCustomer() + { + var result = _dataSource.Company; + + return Ok(result?.VipCustomer); + } + + [EnableQuery] + [HttpGet("odata/Company/Departments")] + public IActionResult GetCompanyDepartments() + { + var result = _dataSource.Company; + + return Ok(result?.Departments); + } + + [EnableQuery] + [HttpGet("odata/Company/Revenue")] + public IActionResult GetRevenue() + { + var result = _dataSource.Company; + + return Ok(result?.Revenue); + } + + [EnableQuery] + [HttpGet("odata/Company/CoreDepartment")] + public IActionResult GetCompanyCoreDepartment() + { + var result = _dataSource.Company; + + return Ok(result?.CoreDepartment); + } + + [EnableQuery] + [HttpGet("odata/Company/Address/City")] + public IActionResult GetCompanyAddressCity() + { + var result = _dataSource.Company; + + return Ok(result?.Address?.City); + } + + #endregion + + #region odata/Boss + [EnableQuery] [HttpGet("odata/Boss")] public IActionResult GetBoss() @@ -124,6 +205,21 @@ public IActionResult GetBossCityFromDerivedType() return Ok(customer.City); } + #endregion + + #region odata/Departments + + [EnableQuery] + [HttpGet("odata/Departments")] + public IActionResult GetDepartments() + { + var result = _dataSource.Departments; + + return Ok(result); + } + + #endregion + [HttpPost("odata/singletontests/Default.ResetDefaultDataSource")] public IActionResult ResetDefaultDataSource() { diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonClientTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonClientTests.cs new file mode 100644 index 0000000000..05b5958f10 --- /dev/null +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonClientTests.cs @@ -0,0 +1,512 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.AspNetCore.OData; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Client.E2E.TestCommon; +using Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Default; +using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; +using Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; +using Xunit; +using Asset = Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Asset; +using Company = Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Company; +using CompanyCategory = Microsoft.OData.Client.E2E.Tests.Common.Client.Default.CompanyCategory; +using Customer = Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Customer; +using Department = Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Department; +using PublicCompany = Microsoft.OData.Client.E2E.Tests.Common.Client.Default.PublicCompany; + +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Tests; + +public class SingletonClientTests : EndToEndTestBase +{ + private readonly Uri _baseUri; + private readonly Container _context; + + public class TestsStartup : TestStartupBase + { + public override void ConfigureServices(IServiceCollection services) + { + services.ConfigureControllers(typeof(SingletonClientTestsController), typeof(MetadataController)); + + services.AddControllers().AddOData(opt => + { + opt.EnableQueryFeatures().AddRouteComponents("odata", DefaultEdmModel.GetEdmModel()); + opt.RouteOptions.EnableNonParenthesisForEmptyParameterFunction = true; + }); + } + } + + public SingletonClientTests(TestWebApplicationFactory fixture) : base(fixture) + { + if (Client.BaseAddress == null) + { + throw new ArgumentNullException(nameof(Client.BaseAddress), "Base address cannot be null"); + } + + _baseUri = new Uri(Client.BaseAddress, "odata/"); + + _context = new Container(_baseUri) + { + HttpClientFactory = HttpClientFactory + }; + + ResetDefaultDataSource(); + } + + #region Singleton Client Tests + + [Fact] + public async Task SingletonClientTestAsync() + { + // Arrange + var rand = new Random(); + var format = ODataFormat.Json; + + //Query Singleton + _context.MergeOption = MergeOption.OverwriteChanges; + var company = await _context.Company.GetValueAsync(); + Assert.NotNull(company); + + //Update Singleton Property and Verify + company.CompanyCategory = CompanyCategory.Communication; + company.Name = "UpdatedName"; + company.Address.City = "UpdatedCity"; + _context.UpdateObject(company); + await _context.SaveChangesAsync(SaveChangesOptions.ReplaceOnUpdate); + + //Query Singleton Property - Select + var companyCategory = await _context.Company.Select(c => c.CompanyCategory).GetValueAsync(); + Assert.Equal(CompanyCategory.Communication, companyCategory); + + var cities = await _context.CreateSingletonQuery("Company/Address/City").ExecuteAsync(); + var city = cities.Single(); + Assert.Equal("UpdatedCity", city); + + var names = await _context.ExecuteAsync(new Uri("Company/Name", UriKind.Relative)); + var name = names.Single(); + Assert.Equal("UpdatedName", name); + + //Projection with properties - Select + company = await _context.Company.Select(c => new Company { CompanyID = c.CompanyID, Address = c.Address, Name = c.Name }).GetValueAsync(); + Assert.NotNull(company); + Assert.Equal("UpdatedName", company.Name); + + //Load Navigation Property + //Singleton + _context.LoadProperty(company, "VipCustomer"); + Assert.NotNull(company.VipCustomer); + + //Collection + await _context.LoadPropertyAsync(company, "Departments"); + Assert.NotNull(company.Departments); + Assert.True(company.Departments.Count > 0); + + //Single Entity + await _context.LoadPropertyAsync(company, "CoreDepartment"); + Assert.NotNull(company.CoreDepartment); + + //Add Navigation Property - Collection + company = await _context.Company.GetValueAsync(); + int tmpDepartmentID = rand.Next(); + int tmpCoreDepartmentID = rand.Next(); + var department = new Department() + { + DepartmentID = tmpDepartmentID, + Name = "ID" + tmpDepartmentID, + }; + var coreDepartment = new Department() + { + DepartmentID = tmpCoreDepartmentID, + Name = "ID" + tmpCoreDepartmentID, + }; + _context.AddToDepartments(department); + _context.AddLink(company, "Departments", department); + await _context.SaveChangesAsync(); + + _context.AddToDepartments(coreDepartment); + _context.AddLink(company, "Departments", coreDepartment); + await _context.SaveChangesAsync(); + + _context.Departments.ToList(); + + //Projection with Navigation properties - Select + company = await _context.Company.Select(c => new Company { CompanyID = c.CompanyID, Departments = c.Departments }).GetValueAsync(); + Assert.NotNull(company); + Assert.Contains(company.Departments, c => c.DepartmentID == tmpDepartmentID); + Assert.Contains(company.Departments, c => c.DepartmentID == tmpCoreDepartmentID); + + //Update Navigation Property - Single Entity + _context.SetLink(company, "CoreDepartment", coreDepartment); + await _context.SaveChangesAsync(); + + //Projection with Navigation properties - Select + company = await _context.Company.Select(c => new Company { CompanyID = c.CompanyID, CoreDepartment = c.CoreDepartment }).GetValueAsync(); + Assert.NotNull(company); + Assert.Equal(company.CoreDepartment.DepartmentID, tmpCoreDepartmentID); + + //Update EntitySet's Navigation Property - Singleton + _context.SetLink(department, "Company", company); + await _context.SaveChangesAsync(SaveChangesOptions.ReplaceOnUpdate); + + //Query(Expand) EntitySet's Navigation Property - Singleton + department = _context.Departments.Expand(d => d.Company).Where(d => d.DepartmentID == tmpDepartmentID).Single(); + Assert.NotNull(department); + Assert.Equal(department.Company.CompanyID, company.CompanyID); + + //Delete Navigation Property - EntitySet + _context.DeleteLink(company, "Departments", department); + await _context.SaveChangesAsync(); + + //Projection with Navigation Property - EntitySet + company.Departments = null; + company = await _context.Company.Select(c => new Company { CompanyID = c.CompanyID, Departments = c.Departments }).GetValueAsync(); + Assert.NotNull(company); + Assert.DoesNotContain(company.Departments, c => c.DepartmentID == tmpDepartmentID); + + //Query Singleton's Navigation Property - Singleton + company = await _context.Company.Select(c => new Company { CompanyID = c.CompanyID, VipCustomer = c.VipCustomer }).GetValueAsync(); + Assert.NotNull(company); + Assert.NotNull(company.VipCustomer); + + //Query Singleton again with Execute + var vipCustomers = await _context.ExecuteAsync(new Uri("VipCustomer", UriKind.Relative)); + var vipCustomer = vipCustomers.Single(); + + //Update Singleton's Navigation property - Singleton + vipCustomer.LastName = "UpdatedLastName"; + _context.UpdateRelatedObject(company, "VipCustomer", vipCustomer); + await _context.SaveChangesAsync(); + + company.VipCustomer = null; + company = await _context.Company.Expand(c => c.VipCustomer).GetValueAsync(); + Assert.NotNull(company); + Assert.NotNull(company.VipCustomer); + Assert.Equal("UpdatedLastName", company.VipCustomer.LastName); + + //Update Navigation Property - Delete the Singleton navigation + _context.SetLink(company, "VipCustomer", null); + await _context.SaveChangesAsync(); + + //Expand Navigation Property - Singleton + company.VipCustomer = null; + company = await _context.Company.Expand(c => c.VipCustomer).GetValueAsync(); + Assert.NotNull(company); + Assert.Null(company.VipCustomer); + + //Update Navigation Property - Singleton + var anotherVipCustomer = await _context.VipCustomer.GetValueAsync(); + _context.SetLink(company, "VipCustomer", anotherVipCustomer); + await _context.SaveChangesAsync(); + company = await _context.Company.Select(c => new Company { CompanyID = c.CompanyID, VipCustomer = c.VipCustomer }).GetValueAsync(); + Assert.NotNull(company); + Assert.NotNull(company.VipCustomer); + + ResetDefaultDataSource(); + } + + [Fact] + public void SingletonClientTest() + { + // Arrange + var rand = new Random(); + var format = ODataFormat.Json; + + //Query Singleton + _context.MergeOption = MergeOption.OverwriteChanges; + var company = _context.Company.GetValue(); + Assert.NotNull(company); + + //Update Singleton Property and Verify + company.CompanyCategory = CompanyCategory.Communication; + company.Name = "UpdatedName"; + company.Address.City = "UpdatedCity"; + _context.UpdateObject(company); + var result = _context.SaveChanges(SaveChangesOptions.ReplaceOnUpdate); + + //Query Singleton Property - Select + var companyCategory = _context.Company.Select(c => c.CompanyCategory).GetValue(); + Assert.Equal(CompanyCategory.Communication, companyCategory); + + var cities = _context.CreateSingletonQuery("Company/Address/City").Execute(); + var city = cities.Single(); + Assert.Equal("UpdatedCity", city); + + var name = _context.Execute(new Uri("Company/Name", UriKind.Relative)).Single(); + Assert.Equal("UpdatedName", name); + + //Projection with properties - Select + company = _context.Company.Select(c => new Company { CompanyID = c.CompanyID, Address = c.Address, Name = c.Name }).GetValue(); + Assert.NotNull(company); + Assert.Equal("UpdatedName", company.Name); + + //Load Navigation Property + //Singleton + _context.LoadProperty(company, "VipCustomer"); + Assert.NotNull(company.VipCustomer); + + //Collection + _context.LoadProperty(company, "Departments"); + Assert.NotNull(company.Departments); + Assert.True(company.Departments.Count > 0); + + //Single Entity + _context.LoadProperty(company, "CoreDepartment"); + Assert.NotNull(company.CoreDepartment); + + //Add Navigation Property - Collection + company = _context.Company.GetValue(); + int tmpDepartmentID = rand.Next(); + int tmpCoreDepartmentID = rand.Next(); + var department = new Department() + { + DepartmentID = tmpDepartmentID, + Name = "ID" + tmpDepartmentID, + }; + var coreDepartment = new Department() + { + DepartmentID = tmpCoreDepartmentID, + Name = "ID" + tmpCoreDepartmentID, + }; + _context.AddToDepartments(department); + _context.AddLink(company, "Departments", department); + result = _context.SaveChanges(); + + _context.AddToDepartments(coreDepartment); + _context.AddLink(company, "Departments", coreDepartment); + result = _context.SaveChanges(); + + _context.Departments.ToList(); + + //Projection with Navigation properties - Select + company = _context.Company.Select(c => new Company { CompanyID = c.CompanyID, Departments = c.Departments }).GetValue(); + Assert.NotNull(company); + Assert.Contains(company.Departments, c => c.DepartmentID == tmpDepartmentID); + Assert.Contains(company.Departments, c => c.DepartmentID == tmpCoreDepartmentID); + + //Update Navigation Property - Single Entity + _context.SetLink(company, "CoreDepartment", coreDepartment); + result = _context.SaveChanges(); + + //Projection with Navigation properties - Select + company = _context.Company.Select(c => new Company { CompanyID = c.CompanyID, CoreDepartment = c.CoreDepartment }).GetValue(); + Assert.NotNull(company); + Assert.Equal(company.CoreDepartment.DepartmentID, tmpCoreDepartmentID); + + //Update EntitySet's Navigation Property - Singleton + _context.SetLink(department, "Company", company); + result = _context.SaveChanges(SaveChangesOptions.ReplaceOnUpdate); + + //Query(Expand) EntitySet's Navigation Property - Singleton + department = _context.Departments.Expand(d => d.Company).Where(d => d.DepartmentID == tmpDepartmentID).Single(); + Assert.NotNull(department); + Assert.Equal(department.Company.CompanyID, company.CompanyID); + + //Delete Navigation Property - EntitySet + _context.DeleteLink(company, "Departments", department); + result = _context.SaveChanges(); + + //Projection with Navigation Property - EntitySet + company.Departments = null; + company = _context.Company.Select(c => new Company { CompanyID = c.CompanyID, Departments = c.Departments }).GetValue(); + Assert.NotNull(company); + Assert.DoesNotContain(company.Departments, c => c.DepartmentID == tmpDepartmentID); + + //Query Singleton's Navigation Property - Singleton + company = _context.Company.Select(c => new Company { CompanyID = c.CompanyID, VipCustomer = c.VipCustomer }).GetValue(); + Assert.NotNull(company); + Assert.NotNull(company.VipCustomer); + + //Query Singleton again with Execute + var vipCustomer = _context.Execute(new Uri("VipCustomer", UriKind.Relative)).Single(); + + //Update Singleton's Navigation property - Singleton + vipCustomer.LastName = "UpdatedLastName"; + _context.UpdateRelatedObject(company, "VipCustomer", vipCustomer); + result = _context.SaveChanges(); + + company.VipCustomer = null; + company = _context.Company.Expand(c => c.VipCustomer).GetValue(); + Assert.NotNull(company); + Assert.NotNull(company.VipCustomer); + Assert.Equal("UpdatedLastName", company.VipCustomer.LastName); + + //Update Navigation Property - Delete the Singleton navigation + _context.SetLink(company, "VipCustomer", null); + result = _context.SaveChanges(); + + //Expand Navigation Property - Singleton + company.VipCustomer = null; + company = _context.Company.Expand(c => c.VipCustomer).GetValue(); + Assert.NotNull(company); + Assert.Null(company.VipCustomer); + + //Update Navigation Property - Singleton + var anotherVipCustomer = _context.VipCustomer.GetValue(); + _context.SetLink(company, "VipCustomer", anotherVipCustomer); + result = _context.SaveChanges(); + company = _context.Company.Select(c => new Company { CompanyID = c.CompanyID, VipCustomer = c.VipCustomer }).GetValue(); + Assert.NotNull(company); + Assert.NotNull(company.VipCustomer); + + ResetDefaultDataSource(); + } + + #endregion + + #region DerivedType Singleton Tests + + [Fact] + public void DerivedTypeSingletonClientTest() + { + // Query Singleton + _context.MergeOption = MergeOption.OverwriteChanges; + var company = _context.PublicCompany.GetValue(); + Assert.NotNull(company); + + // Update DerivedType Property and Verify + var publicCompany = company as PublicCompany; + Assert.NotNull(publicCompany); + publicCompany.Name = "UpdatedName"; + publicCompany.StockExchange = "Updated StockExchange"; + _context.UpdateObject(publicCompany); + _context.SaveChanges(SaveChangesOptions.ReplaceOnUpdate); + + // Query Singleton Property - Select + var name = _context.PublicCompany.Select(c => c.Name).GetValue(); + Assert.Equal("UpdatedName", name); + company = _context.CreateSingletonQuery("PublicCompany").Single(); + Assert.Equal("Updated StockExchange", (company as PublicCompany).StockExchange); + + // Query properties of DerivedType + var stockExchange = _context.PublicCompany.Select(c => (c as PublicCompany).StockExchange).GetValue(); + Assert.Equal("Updated StockExchange", stockExchange); + + // Projection with properties - Select + publicCompany = _context.PublicCompany.Select(c => + new PublicCompany { CompanyID = c.CompanyID, Name = c.Name, StockExchange = (c as PublicCompany).StockExchange }).GetValue(); + Assert.NotNull(publicCompany); + Assert.Equal("UpdatedName", publicCompany.Name); + Assert.Equal("Updated StockExchange", publicCompany.StockExchange); + + company = _context.CreateSingletonQuery("PublicCompany").Single(); + + // Load Navigation Property + // Collection + _context.LoadProperty(company, "Assets"); + Assert.NotNull(((PublicCompany)company).Assets); + Assert.Equal(2, ((PublicCompany)company).Assets.Count); + + // Single Entity + _context.LoadProperty(company, "Club"); + Assert.NotNull(((PublicCompany)company).Club); + + // Singleton + _context.LoadProperty(publicCompany, "LabourUnion"); + Assert.NotNull(((PublicCompany)company).LabourUnion); + + //Add Contained Navigation Property - Collection of derived type + var random = new Random(); + int tmpAssertId = random.Next(); + Asset tmpAssert = new() + { + AssetID = tmpAssertId, + Name = tmpAssertId + "Name", + Number = tmpAssertId + }; + + _context.AddRelatedObject(publicCompany, "Assets", tmpAssert); + _context.SaveChanges(); + + // Query contained Navigation Property - Collection of derived type + company = _context.PublicCompany.Expand(c => (c as PublicCompany).Assets).GetValue(); + Assert.NotNull(company); + Assert.Contains(((PublicCompany)company).Assets, a => a.AssetID == tmpAssertId); + + _context.DeleteObject(tmpAssert); + _context.SaveChanges(); + + company = _context.PublicCompany.Expand(c => (c as PublicCompany).Assets).GetValue(); + Assert.NotNull(company); + Assert.DoesNotContain(((PublicCompany)company).Assets, a => a.AssetID == tmpAssertId); + + // Updated contained Navigation Property - SingleEntity of derived type + var club = ((PublicCompany)company).Club; + club.Name = "UpdatedClubName"; + _context.UpdateRelatedObject(company, "Club", club); + _context.SaveChanges(); + + // Query Contained Navigation Property - Single Entity of derived type + publicCompany = _context.PublicCompany.Select(c => new PublicCompany { CompanyID = c.CompanyID, Club = (c as PublicCompany).Club }).GetValue(); + Assert.NotNull(publicCompany); + Assert.NotNull(publicCompany.Club); + Assert.Equal("UpdatedClubName", publicCompany.Club.Name); + + company = _context.PublicCompany.Expand(c => (c as PublicCompany).Club).GetValue(); + Assert.NotNull(company); + Assert.NotNull(((PublicCompany)company).Club); + + // Projection with Navigation property of derived type - Singleton + company = _context.PublicCompany.Expand(c => (c as PublicCompany).LabourUnion).GetValue(); + + // Update Navigation property of derived Type - Singleton + var labourUnion = ((PublicCompany)company).LabourUnion; + labourUnion.Name = "UpdatedLabourUnionName"; + _context.UpdateRelatedObject(publicCompany, "LabourUnion", labourUnion); + _context.SaveChanges(); + + //Query singleton of derived type. + publicCompany = _context.PublicCompany.Select(c => new PublicCompany { CompanyID = c.CompanyID, LabourUnion = (c as PublicCompany).LabourUnion }).GetValue(); + Assert.NotNull(publicCompany); + Assert.NotNull(publicCompany.LabourUnion); + + ResetDefaultDataSource(); + } + + #endregion + + #region Action/Function + + [Fact] + public void InvokeFunctionBoundedToSingleton() + { + // Arrange & Act & Assert + var employeeCount = _context.Execute(new Uri(_baseUri.AbsoluteUri + "Company/Default.GetEmployeesCount", UriKind.Absolute)).Single(); + Assert.Equal(2, employeeCount); + + var company = _context.Company.GetValue(); + var descriptor = _context.GetEntityDescriptor(company).OperationDescriptors.Single(e => e.Title == "Default.GetEmployeesCount"); + employeeCount = _context.Execute(descriptor.Target, "GET", true).Single(); + Assert.Equal(2, employeeCount); + } + + [Fact] + public void InvokeActionBoundedToSingleton() + { + var company = _context.Company.GetValue(); + _context.LoadProperty(company, "Revenue"); + + var newValue = _context.Execute( + new Uri(_baseUri.AbsoluteUri + "Company/Default.IncreaseRevenue"), "POST", true, new BodyOperationParameter("IncreaseValue", 20000)); + Assert.Equal(newValue.Single(), company.Revenue + 20000); + + OperationDescriptor descriptor = _context.GetEntityDescriptor(company).OperationDescriptors.Single(e => e.Title == "Default.IncreaseRevenue"); + newValue = _context.Execute(descriptor.Target, "POST", new BodyOperationParameter("IncreaseValue", 40000)); + Assert.Equal(newValue.Single(), company.Revenue + 60000); + } + + #endregion + + #region Private methods + + private void ResetDefaultDataSource() + { + var actionUri = new Uri(_baseUri + "singletonclienttests/Default.ResetDefaultDataSource", UriKind.Absolute); + _context.Execute(actionUri, "POST"); + } + + #endregion +} diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs index 0544628660..193ad81e57 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs @@ -335,6 +335,8 @@ public async Task QuerySingletonProperty(string mimeType) [MemberData(nameof(MimeTypesData))] public async Task QuerySingletonPropertyUnderComplexProperty(string mimeType) { + ResetDefaultDataSource(); + // Arrange & Act var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/HomeAddress/City", mimeType); diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs new file mode 100644 index 0000000000..26adc370d6 --- /dev/null +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs @@ -0,0 +1,248 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.AspNetCore.OData; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OData.Client.E2E.TestCommon; +using Microsoft.OData.Client.E2E.TestCommon.Common; +using Microsoft.OData.Client.E2E.TestCommon.Helpers; +using Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Default; +using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; +using Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; +using Microsoft.OData.Edm; +using Xunit; + +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Tests; + +public class SingletonUpdateTests : EndToEndTestBase +{ + private const string NameSpacePrefix = "Microsoft.OData.Client.E2E.Tests.Common.Server.Default"; + + private readonly Uri _baseUri; + private readonly Container _context; + private readonly IEdmModel _model; + + public class TestsStartup : TestStartupBase + { + public override void ConfigureServices(IServiceCollection services) + { + services.ConfigureControllers(typeof(SingletonTestsController), typeof(MetadataController)); + + services.AddControllers().AddOData(opt => + opt.EnableQueryFeatures().AddRouteComponents("odata", DefaultEdmModel.GetEdmModel())); + } + } + + public SingletonUpdateTests(TestWebApplicationFactory fixture) : base(fixture) + { + if (Client.BaseAddress == null) + { + throw new ArgumentNullException(nameof(Client.BaseAddress), "Base address cannot be null"); + } + + _baseUri = new Uri(Client.BaseAddress, "odata/"); + + _context = new Container(_baseUri) + { + HttpClientFactory = HttpClientFactory + }; + + _model = DefaultEdmModel.GetEdmModel(); + ResetDefaultDataSource(); + } + + public static IEnumerable MimeTypesData + { + get + { + yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata }; + yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata }; + yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterNoMetadata }; + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task UpdateSingletonProperty(string mimeType) + { + ResetDefaultDataSource(); + + // Arrange + var cities = new Dictionary + { + { MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata, "Seattle" }, + { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata, "Paris" }, + { MimeTypes.ApplicationJson + MimeTypes.ODataParameterNoMetadata, "New York" } + }; + + // Act + var entries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); + var customerEntry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + // Assert + Assert.NotNull(customerEntry); + var cityProperty = customerEntry.Properties.Single(p => p.Name == "City") as ODataProperty; + Assert.NotNull(cityProperty); + Assert.Equal("London", cityProperty.Value); + } + + // Arrange + var properties = new[] { new ODataProperty { Name = "City", Value = cities[mimeType] } }; + + // Act + await this.UpdateEntryAsync("Customer", "VipCustomer", mimeType, properties); + var updatedEntries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); + + // Assert + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + var updatedCustomerEntry = updatedEntries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + Assert.NotNull(updatedCustomerEntry); + + var cityProperty = updatedCustomerEntry.Properties.Single(p => p.Name == "City") as ODataProperty; + Assert.NotNull(cityProperty); + Assert.Equal(cities[mimeType], cityProperty.Value); + } + } + + [Theory] + [MemberData(nameof(MimeTypesData))] + public async Task UpdateSingletonComplexProperty(string mimeType) + { + // Arrange + var complex0 = new ODataResource() + { + TypeName = $"{NameSpacePrefix}.Address", + Properties = + [ + new ODataProperty() {Name = "Street", Value = "1 Microsoft Way"}, + new ODataProperty() {Name = "City", Value = "London"}, + new ODataProperty() {Name = "PostalCode", Value = "98052"}, + new ODataProperty() {Name = "UpdatedTime", Value = DateTimeOffset.Parse("1/1/0001 12:00:00 AM +00:00")} + ] + }; + + var homeAddress0 = new ODataNestedResourceInfo() { Name = "HomeAddress", IsCollection = false }; + homeAddress0.SetAnnotation(complex0); + + var complex1 = new ODataResource() + { + TypeName = $"{NameSpacePrefix}.Address", + Properties = + [ + new ODataProperty() {Name = "Street", Value = "Zixing 999"}, + new ODataProperty() {Name = "City", Value = "Seattle"}, + new ODataProperty() {Name = "PostalCode", Value = "1111"}, + new ODataProperty() {Name = "UpdatedTime", Value = DateTimeOffset.Parse("1/1/0001 12:00:00 AM +00:00")} + ] + }; + + var homeAddress1 = new ODataNestedResourceInfo() { Name = "HomeAddress", IsCollection = false }; + homeAddress1.SetAnnotation(complex1); + + var currentHomeAddress = complex0; + var updatedHomeAddress = complex1; + var properties = new[] { homeAddress1 }; + + if(mimeType.Equals(MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata)) + { + currentHomeAddress = complex0; + updatedHomeAddress = complex0; + properties = new[] { homeAddress0 }; + } + + // Act + var entries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + // Assert + ODataValueAssertEqualHelper.AssertODataPropertyAndResourceEqual(currentHomeAddress, entries[0]); + } + + // Act + await this.UpdateEntryAsync("Customer", "VipCustomer", mimeType, properties); + + var updatedEntries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); + if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + { + // Assert + ODataValueAssertEqualHelper.AssertODataPropertyAndResourceEqual(updatedHomeAddress, updatedEntries[0]); + } + + ResetDefaultDataSource(); + } + + #region Private methods + + private ODataMessageReaderTestsHelper TestsHelper + { + get + { + return new ODataMessageReaderTestsHelper(_baseUri, _model, Client); + } + } + + private async Task UpdateEntryAsync(string singletonType, string singletonName, string mimeType, IEnumerable properties) + { + var entry = new ODataResource() { TypeName = $"{NameSpacePrefix}.{singletonType}" }; + var elementType = properties != null && properties.Any() ? properties.ElementAt(0).GetType() : null; + + Assert.NotNull(properties); + + if (elementType == typeof(ODataProperty)) + { + entry.Properties = properties.Cast(); + } + + var settings = new ODataMessageWriterSettings + { + BaseUri = _baseUri, + EnableMessageStreamDisposal = false, // Ensure the stream is not disposed of prematurely + }; + + var customerType = _model.FindDeclaredType(NameSpacePrefix + singletonType) as IEdmEntityType; + var customerSet = _model.EntityContainer.FindSingleton(singletonName); + + var requestMessage = new TestHttpClientRequestMessage(new Uri(_baseUri + singletonName), Client); + requestMessage.SetHeader("Content-Type", mimeType); + requestMessage.SetHeader("Accept", mimeType); + requestMessage.Method = "PATCH"; + + using (var messageWriter = new ODataMessageWriter(requestMessage, settings)) + { + var odataWriter = await messageWriter.CreateODataResourceWriterAsync(customerSet, customerType); + await odataWriter.WriteStartAsync(entry); + if (elementType == typeof(ODataNestedResourceInfo)) + { + foreach (var p in properties) + { + var nestedInfo = (ODataNestedResourceInfo)p; + await odataWriter.WriteStartAsync(nestedInfo); + await odataWriter.WriteStartAsync(nestedInfo.GetAnnotation()); + await odataWriter.WriteEndAsync(); + await odataWriter.WriteEndAsync(); + } + } + await odataWriter.WriteEndAsync(); + } + + var responseMessage = await requestMessage.GetResponseAsync(); + + // verify the update + Assert.Equal(204, responseMessage.StatusCode); + } + + private void ResetDefaultDataSource() + { + var actionUri = new Uri(_baseUri + "singletontests/Default.ResetDefaultDataSource", UriKind.Absolute); + _context.Execute(actionUri, "POST"); + } + + #endregion +} From ae5a5324c2aaa864780952e4efcfbe8f920c7081 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Wed, 15 Jan 2025 17:40:56 +0300 Subject: [PATCH 3/5] Refactor tests --- .../Server/SingletonTestsController.cs | 2 - .../Server/SingletonUpdateTestsController.cs | 53 +++ .../Tests/SingletonQueryTests.cs | 416 ++++++++---------- .../Tests/SingletonTestsHelper.cs} | 10 +- .../Tests/SingletonUpdateTests.cs | 63 ++- 5 files changed, 278 insertions(+), 266 deletions(-) create mode 100644 test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonUpdateTestsController.cs rename test/EndToEndTests/{Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs => Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs} (95%) diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs index 447c68e86b..35718db9fe 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonTestsController.cs @@ -4,10 +4,8 @@ // //--------------------------------------------------------------------- -using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OData.Deltas; -using Microsoft.AspNetCore.OData.Formatter; using Microsoft.AspNetCore.OData.Query; using Microsoft.AspNetCore.OData.Routing.Controllers; using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonUpdateTestsController.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonUpdateTestsController.cs new file mode 100644 index 0000000000..e34e7c4019 --- /dev/null +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Server/SingletonUpdateTestsController.cs @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.OData.Deltas; +using Microsoft.AspNetCore.OData.Query; +using Microsoft.AspNetCore.OData.Routing.Controllers; +using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; + +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; + +public class SingletonUpdateTestsController : ODataController +{ + private static DefaultDataSource _dataSource; + + + #region odata/VipCustomer + + [EnableQuery] + [HttpGet("odata/VipCustomer")] + public IActionResult GetVipCustomer() + { + var result = _dataSource.VipCustomer; + + return Ok(result); + } + + [HttpPatch("odata/VipCustomer")] + public IActionResult UpdateVipCustomer([FromBody] Delta delta) + { + var customer = _dataSource.VipCustomer; + if (customer == null) + { + return NotFound(); + } + + var updatedResult = delta.Patch(customer); + return Updated(updatedResult); + } + + #endregion + + [HttpPost("odata/singletonupdatetests/Default.ResetDefaultDataSource")] + public IActionResult ResetDefaultDataSource() + { + _dataSource = DefaultDataSource.CreateInstance(); + + return Ok(); + } +} diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs index 193ad81e57..9f0cdf0968 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonQueryTests.cs @@ -9,7 +9,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Client.E2E.TestCommon; using Microsoft.OData.Client.E2E.TestCommon.Common; -using Microsoft.OData.Client.E2E.TestCommon.Helpers; using Microsoft.OData.Client.E2E.Tests.Common.Client.Default.Default; using Microsoft.OData.Client.E2E.Tests.Common.Server.Default; using Microsoft.OData.Client.E2E.Tests.SingletonTests.Server; @@ -53,20 +52,15 @@ public SingletonQueryTests(TestWebApplicationFactory fixture) : ba ResetDefaultDataSource(); } - public static IEnumerable MimeTypesData - { - get - { - yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata }; - yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata }; - yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterNoMetadata }; - } - } + // Constants + private const string MimeTypeODataParameterFullMetadata = MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata; + private const string MimeTypeODataParameterMinimalMetadata = MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata; #region Query singleton entity [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingleton(string mimeType) { // Arrange & Act @@ -74,29 +68,27 @@ public async Task QuerySingleton(string mimeType) var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(entry); - - var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; - var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; - var lastNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "LastName") as ODataProperty; - var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; - var emailsProperty = entry.Properties.SingleOrDefault(p => p.Name == "Emails") as ODataProperty; - Assert.NotNull(personIDProperty); - Assert.NotNull(firstNameProperty); - Assert.NotNull(lastNameProperty); - Assert.NotNull(cityProperty); - - Assert.Equal(1, personIDProperty.Value); - Assert.Equal("Bob", firstNameProperty.Value); - Assert.Equal("Cat", lastNameProperty.Value); - Assert.Equal("London", cityProperty.Value); - } + Assert.NotNull(entry); + + var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var lastNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "LastName") as ODataProperty; + var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + var emailsProperty = entry.Properties.SingleOrDefault(p => p.Name == "Emails") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(lastNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(1, personIDProperty.Value); + Assert.Equal("Bob", firstNameProperty.Value); + Assert.Equal("Cat", lastNameProperty.Value); + Assert.Equal("London", cityProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonWhichIsOpenType(string mimeType) { // Arrange & Act @@ -104,25 +96,23 @@ public async Task QuerySingletonWhichIsOpenType(string mimeType) var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Company")); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(entry); - - var companyIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "CompanyID") as ODataProperty; - var companyCategoryProperty = entry.Properties.SingleOrDefault(p => p.Name == "CompanyCategory") as ODataProperty; - var nameProperty = entry.Properties.SingleOrDefault(p => p.Name == "Name") as ODataProperty; - Assert.NotNull(companyIDProperty); - Assert.NotNull(companyCategoryProperty); - Assert.NotNull(nameProperty); - - Assert.Equal(0, companyIDProperty.Value); - Assert.Equal("IT", companyCategoryProperty.Value.ToString()); - Assert.Equal("MS", nameProperty.Value); - } + Assert.NotNull(entry); + + var companyIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "CompanyID") as ODataProperty; + var companyCategoryProperty = entry.Properties.SingleOrDefault(p => p.Name == "CompanyCategory") as ODataProperty; + var nameProperty = entry.Properties.SingleOrDefault(p => p.Name == "Name") as ODataProperty; + Assert.NotNull(companyIDProperty); + Assert.NotNull(companyCategoryProperty); + Assert.NotNull(nameProperty); + + Assert.Equal(0, companyIDProperty.Value); + Assert.Equal("IT", companyCategoryProperty.Value.ToString()); + Assert.Equal("MS", nameProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QueryDerivedSingletonWithTypeCast(string mimeType) { // Arrange & Act @@ -130,28 +120,26 @@ public async Task QueryDerivedSingletonWithTypeCast(string mimeType) var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(entry); - - var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; - var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; - var lastNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "LastName") as ODataProperty; - var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; - Assert.NotNull(personIDProperty); - Assert.NotNull(firstNameProperty); - Assert.NotNull(lastNameProperty); - Assert.NotNull(cityProperty); - - Assert.Equal(2, personIDProperty.Value); - Assert.Equal("Jill", firstNameProperty.Value); - Assert.Equal("Jones", lastNameProperty.Value); - Assert.Equal("Sydney", cityProperty.Value); - } + Assert.NotNull(entry); + + var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var lastNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "LastName") as ODataProperty; + var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(lastNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(2, personIDProperty.Value); + Assert.Equal("Jill", firstNameProperty.Value); + Assert.Equal("Jones", lastNameProperty.Value); + Assert.Equal("Sydney", cityProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QueryDerivedSingletonWithoutTypeCast(string mimeType) { // Arrange & Act @@ -159,25 +147,23 @@ public async Task QueryDerivedSingletonWithoutTypeCast(string mimeType) var entry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(entry); - - var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; - var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; - var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; - Assert.NotNull(personIDProperty); - Assert.NotNull(firstNameProperty); - Assert.NotNull(cityProperty); - - Assert.Equal(2, personIDProperty.Value); - Assert.Equal("Jill", firstNameProperty.Value); - Assert.Equal("Sydney", cityProperty.Value); - } + Assert.NotNull(entry); + + var personIDProperty = entry.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = entry.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var cityProperty = entry.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(2, personIDProperty.Value); + Assert.Equal("Jill", firstNameProperty.Value); + Assert.Equal("Sydney", cityProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonWithExpand(string mimeType) { // Arrange & Act @@ -185,29 +171,27 @@ public async Task QuerySingletonWithExpand(string mimeType) var entries = resources.Where(r => r != null && r.Id != null).ToList(); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - var orders = entries.FindAll(e => e.Id.AbsoluteUri.Contains("Orders")); - Assert.Equal(2, orders.Count); - - var customer = entries.SingleOrDefault(e => e.Id.AbsoluteUri.Contains("VipCustomer")); - Assert.NotNull(customer); - - var personIDProperty = customer.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; - var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; - var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; - Assert.NotNull(personIDProperty); - Assert.NotNull(firstNameProperty); - Assert.NotNull(cityProperty); - - Assert.Equal(1, personIDProperty.Value); - Assert.Equal("Bob", firstNameProperty.Value); - Assert.Equal("London", cityProperty.Value); - } + var orders = entries.FindAll(e => e.Id.AbsoluteUri.Contains("Orders")); + Assert.Equal(2, orders.Count); + + var customer = entries.SingleOrDefault(e => e.Id.AbsoluteUri.Contains("VipCustomer")); + Assert.NotNull(customer); + + var personIDProperty = customer.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(1, personIDProperty.Value); + Assert.Equal("Bob", firstNameProperty.Value); + Assert.Equal("London", cityProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonWithSelect(string mimeType) { // Arrange & Act @@ -215,15 +199,13 @@ public async Task QuerySingletonWithSelect(string mimeType) var customer = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(customer); - Assert.Equal(2, customer.Properties.Count()); - } + Assert.NotNull(customer); + Assert.Equal(2, customer.Properties.Count()); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QueryDerivedSingletonWithTypeCastAndSelect(string mimeType) { // Arrange & Act @@ -231,18 +213,16 @@ public async Task QueryDerivedSingletonWithTypeCastAndSelect(string mimeType) var customer = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(customer); - Assert.Single(customer.Properties); - var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; - Assert.NotNull(cityProperty); - Assert.Equal("Sydney", cityProperty.Value); - } + Assert.NotNull(customer); + Assert.Single(customer.Properties); + var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(cityProperty); + Assert.Equal("Sydney", cityProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonWithSelectUnderExpand(string mimeType) { // Arrange & Act @@ -250,36 +230,34 @@ public async Task QuerySingletonWithSelectUnderExpand(string mimeType) var entries = resources.Where(r => r != null && r.Id != null).ToList(); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) + var orders = entries.FindAll(e => e != null && e.Id.AbsoluteUri.Contains("Orders")); + Assert.Equal(2, orders.Count); + + foreach (var order in orders) { - var orders = entries.FindAll(e => e != null && e.Id.AbsoluteUri.Contains("Orders")); - Assert.Equal(2, orders.Count); - - foreach (var order in orders) - { - Assert.Equal(2, order.Properties.Count()); - Assert.Contains(order.Properties, p => p.Name == "OrderID"); - Assert.Contains(order.Properties, p => p.Name == "OrderDate"); - } - - var customer = entries.SingleOrDefault(e => e != null && e.Id.AbsoluteUri.Contains("VipCustomer")); - Assert.NotNull(customer); - - var personIDProperty = customer.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; - var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; - var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; - Assert.NotNull(personIDProperty); - Assert.NotNull(firstNameProperty); - Assert.NotNull(cityProperty); - - Assert.Equal(1, personIDProperty.Value); - Assert.Equal("Bob", firstNameProperty.Value); - Assert.Equal("London", cityProperty.Value); + Assert.Equal(2, order.Properties.Count()); + Assert.Contains(order.Properties, p => p.Name == "OrderID"); + Assert.Contains(order.Properties, p => p.Name == "OrderDate"); } + + var customer = entries.SingleOrDefault(e => e != null && e.Id.AbsoluteUri.Contains("VipCustomer")); + Assert.NotNull(customer); + + var personIDProperty = customer.Properties.SingleOrDefault(p => p.Name == "PersonID") as ODataProperty; + var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + var cityProperty = customer.Properties.SingleOrDefault(p => p.Name == "City") as ODataProperty; + Assert.NotNull(personIDProperty); + Assert.NotNull(firstNameProperty); + Assert.NotNull(cityProperty); + + Assert.Equal(1, personIDProperty.Value); + Assert.Equal("Bob", firstNameProperty.Value); + Assert.Equal("London", cityProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonWithMiscQueryOptions(string mimeType) { // Arrange & Act @@ -287,29 +265,42 @@ public async Task QuerySingletonWithMiscQueryOptions(string mimeType) var entries = resources.Where(r => r != null && r.Id != null).ToList(); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - var orders = entries.FindAll(e => e.Id.AbsoluteUri.Contains("Orders")); - Assert.Equal(2, orders.Count); - - var customer = entries.SingleOrDefault(e => e.Id.AbsoluteUri.Contains("VipCustomer")); - Assert.NotNull(customer); - Assert.Single(customer.Properties); - - var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; - Assert.NotNull(firstNameProperty); - Assert.Equal("Bob", firstNameProperty.Value); - - var homeAddress = resources.SingleOrDefault(r => r != null && r.TypeName.EndsWith("Address")); - Assert.NotNull(homeAddress); - } + var orders = entries.FindAll(e => e.Id.AbsoluteUri.Contains("Orders")); + Assert.Equal(2, orders.Count); + + var customer = entries.SingleOrDefault(e => e.Id.AbsoluteUri.Contains("VipCustomer")); + Assert.NotNull(customer); + Assert.Single(customer.Properties); + + var firstNameProperty = customer.Properties.SingleOrDefault(p => p.Name == "FirstName") as ODataProperty; + Assert.NotNull(firstNameProperty); + Assert.Equal("Bob", firstNameProperty.Value); + + var homeAddress = resources.SingleOrDefault(r => r != null && r.TypeName.EndsWith("Address")); + Assert.NotNull(homeAddress); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task SelectDerivedPropertyWithoutTypeCastShouldFail(string mimeType) { - await this.BadRequestOrNotFoundAsync("Boss?$select=City", mimeType, /* Bad Request */ 400); + // Arrange + ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri }; + var requestUrl = new Uri(_baseUri.AbsoluteUri + "Boss?$select=City", UriKind.Absolute); + + var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); + requestMessage.SetHeader("Accept", mimeType); + if (mimeType == MimeTypes.ApplicationAtomXml) + { + requestMessage.SetHeader("Accept", "text/html, application/xhtml+xml, */*"); + } + + // Act + var responseMessage = await requestMessage.GetResponseAsync(); + + // Assert + Assert.Equal(400, responseMessage.StatusCode); } #endregion @@ -317,22 +308,21 @@ public async Task SelectDerivedPropertyWithoutTypeCastShouldFail(string mimeType #region Query singleton property [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonProperty(string mimeType) { // Arrange & Act var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/PersonID", mimeType); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(property); - Assert.Equal(1, property.Value); - } + Assert.NotNull(property); + Assert.Equal(1, property.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonPropertyUnderComplexProperty(string mimeType) { ResetDefaultDataSource(); @@ -341,30 +331,26 @@ public async Task QuerySingletonPropertyUnderComplexProperty(string mimeType) var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/HomeAddress/City", mimeType); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(property); - Assert.Equal("London", property.Value); - } + Assert.NotNull(property); + Assert.Equal("London", property.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonEnumProperty(string mimeType) { // Arrange & Act var property = await this.TestsHelper.QueryPropertyAsync("Company/CompanyCategory", mimeType); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(property); - Assert.Equal("IT", property.Value.ToString()); - } + Assert.NotNull(property); + Assert.Equal("IT", property.Value.ToString()); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonNavigationProperty(string mimeType) { // Arrange & Act @@ -372,47 +358,41 @@ public async Task QuerySingletonNavigationProperty(string mimeType) var entry = entries.FirstOrDefault(); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(entry); - var orderIdProperty = entry.Properties.Single(p => p.Name == "OrderID") as ODataProperty; - Assert.NotNull(orderIdProperty); - Assert.Equal(7, orderIdProperty.Value); - } + Assert.NotNull(entry); + var orderIdProperty = entry.Properties.Single(p => p.Name == "OrderID") as ODataProperty; + Assert.NotNull(orderIdProperty); + Assert.Equal(7, orderIdProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonPropertyUnderNavigationProperty(string mimeType) { // Arrange & Act var property = await this.TestsHelper.QueryPropertyAsync("VipCustomer/Orders(8)/OrderDate", mimeType); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(property); - Assert.Equal(new DateTimeOffset(2011, 3, 4, 16, 3, 57, TimeSpan.FromHours(-8)), property.Value); - } + Assert.NotNull(property); + Assert.Equal(new DateTimeOffset(2011, 3, 4, 16, 3, 57, TimeSpan.FromHours(-8)), property.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QueryDerivedSingletonPropertyWithTypeCast(string mimeType) { // Arrange & Act var property = await this.TestsHelper.QueryPropertyAsync("Boss/Microsoft.OData.Client.E2E.Tests.Common.Server.Default.Customer/City", mimeType); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(property); - Assert.Equal("Sydney", property.Value); - } + Assert.NotNull(property); + Assert.Equal("Sydney", property.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QuerySingletonNavigationPropertyWithFilter(string mimeType) { // Arrange & Act @@ -420,31 +400,20 @@ public async Task QuerySingletonNavigationPropertyWithFilter(string mimeType) var entry = entries.FirstOrDefault(); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - Assert.NotNull(entry); - var orderDateProperty = entry.Properties.Single(p => p.Name == "OrderDate") as ODataProperty; - Assert.NotNull(orderDateProperty); - Assert.Equal(new DateTimeOffset(2011, 3, 4, 16, 3, 57, TimeSpan.FromHours(-8)), orderDateProperty.Value); - } + Assert.NotNull(entry); + var orderDateProperty = entry.Properties.Single(p => p.Name == "OrderDate") as ODataProperty; + Assert.NotNull(orderDateProperty); + Assert.Equal(new DateTimeOffset(2011, 3, 4, 16, 3, 57, TimeSpan.FromHours(-8)), orderDateProperty.Value); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task QueryDerivedPropertyWithoutTypeCastShouldFail(string mimeType) { - // Arrange & Act & Assert - await this.BadRequestOrNotFoundAsync("Boss/City", mimeType, /* Not Found */ 404); - } - - #endregion - - #region Private methods - - private async Task BadRequestOrNotFoundAsync(string requestUri, string mimeType, int errorCode) - { + // Arrange ODataMessageReaderSettings readerSettings = new() { BaseUri = _baseUri }; - var requestUrl = new Uri(_baseUri.AbsoluteUri + requestUri, UriKind.Absolute); + var requestUrl = new Uri(_baseUri.AbsoluteUri + "Boss/City", UriKind.Absolute); var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); requestMessage.SetHeader("Accept", mimeType); @@ -455,14 +424,19 @@ private async Task BadRequestOrNotFoundAsync(string requestUri, string mimeType, var responseMessage = await requestMessage.GetResponseAsync(); - Assert.Equal(errorCode, responseMessage.StatusCode); + // Assert + Assert.Equal(404, responseMessage.StatusCode); } - private ODataMessageReaderTestsHelper TestsHelper + #endregion + + #region Private methods + + private SingletonTestsHelper TestsHelper { get { - return new ODataMessageReaderTestsHelper(_baseUri, _model, Client); + return new SingletonTestsHelper(_baseUri, _model, Client); } } diff --git a/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs similarity index 95% rename from test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs rename to test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs index 241bd0b457..bbcb0bb4cc 100644 --- a/test/EndToEndTests/Common/Microsoft.OData.Client.E2E.TestCommon/Helpers/ODataMessageReaderTestsHelper.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs @@ -1,5 +1,5 @@ //--------------------------------------------------------------------- -// +// // Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. // //--------------------------------------------------------------------- @@ -8,16 +8,16 @@ using Microsoft.OData.Edm; using Xunit; -namespace Microsoft.OData.Client.E2E.TestCommon.Helpers; +namespace Microsoft.OData.Client.E2E.Tests.SingletonTests.Tests; -public class ODataMessageReaderTestsHelper +public class SingletonTestsHelper { private readonly Uri BaseUri; private readonly IEdmModel Model; private readonly HttpClient Client; private const string IncludeAnnotation = "odata.include-annotations"; - public ODataMessageReaderTestsHelper(Uri baseUri, IEdmModel model, HttpClient client) + public SingletonTestsHelper(Uri baseUri, IEdmModel model, HttpClient client) { this.BaseUri = baseUri; this.Model = model; @@ -101,7 +101,6 @@ public async Task> QueryResourceSetsAsync(string queryText, } Assert.Equal(ODataReaderState.Completed, reader.State); } - } return entries; @@ -140,7 +139,6 @@ public async Task> QueryResourceSetsAsync(string queryText, } Assert.Equal(ODataReaderState.Completed, reader.State); } - } return (resourceEntries, resourceSetEntries); diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs index 26adc370d6..a7ea9e1cd5 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs @@ -30,7 +30,7 @@ public class TestsStartup : TestStartupBase { public override void ConfigureServices(IServiceCollection services) { - services.ConfigureControllers(typeof(SingletonTestsController), typeof(MetadataController)); + services.ConfigureControllers(typeof(SingletonUpdateTestsController), typeof(MetadataController)); services.AddControllers().AddOData(opt => opt.EnableQueryFeatures().AddRouteComponents("odata", DefaultEdmModel.GetEdmModel())); @@ -55,64 +55,53 @@ public SingletonUpdateTests(TestWebApplicationFactory fixture) : b ResetDefaultDataSource(); } - public static IEnumerable MimeTypesData - { - get - { - yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata }; - yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata }; - yield return new object[] { MimeTypes.ApplicationJson + MimeTypes.ODataParameterNoMetadata }; - } - } + // Constants + private const string MimeTypeODataParameterFullMetadata = MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata; + private const string MimeTypeODataParameterMinimalMetadata = MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata; [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task UpdateSingletonProperty(string mimeType) { - ResetDefaultDataSource(); - // Arrange var cities = new Dictionary { { MimeTypes.ApplicationJson + MimeTypes.ODataParameterFullMetadata, "Seattle" }, - { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata, "Paris" }, - { MimeTypes.ApplicationJson + MimeTypes.ODataParameterNoMetadata, "New York" } + { MimeTypes.ApplicationJson + MimeTypes.ODataParameterMinimalMetadata, "Paris" } }; - // Act + // Act (Query) var entries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); var customerEntry = entries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - // Assert - Assert.NotNull(customerEntry); - var cityProperty = customerEntry.Properties.Single(p => p.Name == "City") as ODataProperty; - Assert.NotNull(cityProperty); - Assert.Equal("London", cityProperty.Value); - } + // Assert + Assert.NotNull(customerEntry); + var cityProperty0 = customerEntry.Properties.Single(p => p.Name == "City") as ODataProperty; + Assert.NotNull(cityProperty0); + Assert.Equal("London", cityProperty0.Value); // Arrange var properties = new[] { new ODataProperty { Name = "City", Value = cities[mimeType] } }; - // Act + // Act (Update) await this.UpdateEntryAsync("Customer", "VipCustomer", mimeType, properties); var updatedEntries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); // Assert - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - var updatedCustomerEntry = updatedEntries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); - Assert.NotNull(updatedCustomerEntry); + var updatedCustomerEntry = updatedEntries.SingleOrDefault(e => e != null && e.TypeName.EndsWith("Customer")); + Assert.NotNull(updatedCustomerEntry); - var cityProperty = updatedCustomerEntry.Properties.Single(p => p.Name == "City") as ODataProperty; - Assert.NotNull(cityProperty); - Assert.Equal(cities[mimeType], cityProperty.Value); - } + var cityProperty1 = updatedCustomerEntry.Properties.Single(p => p.Name == "City") as ODataProperty; + Assert.NotNull(cityProperty1); + Assert.Equal(cities[mimeType], cityProperty1.Value); + + ResetDefaultDataSource(); } [Theory] - [MemberData(nameof(MimeTypesData))] + [InlineData(MimeTypeODataParameterFullMetadata)] + [InlineData(MimeTypeODataParameterMinimalMetadata)] public async Task UpdateSingletonComplexProperty(string mimeType) { // Arrange @@ -180,11 +169,11 @@ public async Task UpdateSingletonComplexProperty(string mimeType) #region Private methods - private ODataMessageReaderTestsHelper TestsHelper + private SingletonTestsHelper TestsHelper { get { - return new ODataMessageReaderTestsHelper(_baseUri, _model, Client); + return new SingletonTestsHelper(_baseUri, _model, Client); } } @@ -240,7 +229,7 @@ private async Task UpdateEntryAsync(string singletonType, string singletonName, private void ResetDefaultDataSource() { - var actionUri = new Uri(_baseUri + "singletontests/Default.ResetDefaultDataSource", UriKind.Absolute); + var actionUri = new Uri(_baseUri + "singletonupdatetests/Default.ResetDefaultDataSource", UriKind.Absolute); _context.Execute(actionUri, "POST"); } From 8a673cabb24f67adcfaadb8f9dec4001acd9ee74 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Wed, 15 Jan 2025 17:47:44 +0300 Subject: [PATCH 4/5] Refactor further --- .../SingletonTests/Tests/SingletonUpdateTests.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs index a7ea9e1cd5..11fec999f5 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonUpdateTests.cs @@ -148,21 +148,17 @@ public async Task UpdateSingletonComplexProperty(string mimeType) // Act var entries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - // Assert - ODataValueAssertEqualHelper.AssertODataPropertyAndResourceEqual(currentHomeAddress, entries[0]); - } + + // Assert + ODataValueAssertEqualHelper.AssertODataPropertyAndResourceEqual(currentHomeAddress, entries[0]); // Act await this.UpdateEntryAsync("Customer", "VipCustomer", mimeType, properties); var updatedEntries = await TestsHelper.QueryResourceEntriesAsync("VipCustomer", mimeType); - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - // Assert - ODataValueAssertEqualHelper.AssertODataPropertyAndResourceEqual(updatedHomeAddress, updatedEntries[0]); - } + + // Assert + ODataValueAssertEqualHelper.AssertODataPropertyAndResourceEqual(updatedHomeAddress, updatedEntries[0]); ResetDefaultDataSource(); } From 2d8b10b0e83931c1328dbd8d76cd246717f7f17c Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 20 Jan 2025 19:54:58 +0300 Subject: [PATCH 5/5] Remove unused helper methods --- .../Tests/SingletonTestsHelper.cs | 94 ------------------- 1 file changed, 94 deletions(-) diff --git a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs index bbcb0bb4cc..b96a54e060 100644 --- a/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs +++ b/test/EndToEndTests/Tests/Client/Microsoft.OData.Client.E2E.Tests/SingletonTests/Tests/SingletonTestsHelper.cs @@ -31,7 +31,6 @@ public SingletonTestsHelper(Uri baseUri, IEdmModel model, HttpClient client) /// The MIME type to set in the request header. /// A task that represents the asynchronous operation. The task result contains a list of . public async Task> QueryResourceEntriesAsync(string queryText, string mimeType) - { ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); @@ -106,80 +105,6 @@ public async Task> QueryResourceSetsAsync(string queryText, return entries; } - public async Task<(List, List)> QueryResourceAndResourceSetsAsync(string queryText, string mimeType) - - { - ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; - var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); - - var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); - requestMessage.SetHeader("Accept", mimeType); - - var responseMessage = await requestMessage.GetResponseAsync(); - - Assert.Equal(200, responseMessage.StatusCode); - - var resourceEntries = new List(); - var resourceSetEntries = new List(); - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) - { - var reader = await messageReader.CreateODataResourceSetReaderAsync(); - while (await reader.ReadAsync()) - { - if (reader.State == ODataReaderState.ResourceEnd && reader.Item is ODataResource odataResource) - { - resourceEntries.Add(odataResource); - } - else if (reader.State == ODataReaderState.ResourceSetEnd && reader.Item is ODataResourceSet odataResourceSet) - { - resourceSetEntries.Add(odataResourceSet); - } - } - Assert.Equal(ODataReaderState.Completed, reader.State); - } - } - - return (resourceEntries, resourceSetEntries); - } - - /// - /// Queries an OData resource set asynchronously based on the provided query text and MIME type. - /// - /// The query text to append to the base URI. - /// The MIME type to set in the request header. - /// A task that represents the asynchronous operation. The task result contains an if found; otherwise, null. - public async Task QueryODataResourceSetAsync(string queryText, string mimeType) - { - ODataMessageReaderSettings readerSettings = new() { BaseUri = BaseUri }; - var requestUrl = new Uri(BaseUri.AbsoluteUri + queryText, UriKind.Absolute); - - var requestMessage = new TestHttpClientRequestMessage(requestUrl, Client); - requestMessage.SetHeader("Accept", mimeType); - - var responseMessage = await requestMessage.GetResponseAsync(); - - Assert.Equal(200, responseMessage.StatusCode); - - if (!mimeType.Contains(MimeTypes.ODataParameterNoMetadata)) - { - using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) - { - var reader = await messageReader.CreateODataResourceReaderAsync(); - while (await reader.ReadAsync()) - { - if (reader.State == ODataReaderState.ResourceSetEnd && reader.Item is ODataResourceSet oDataResourceSet) - { - return oDataResourceSet; - } - } - } - } - - return null; - } - /// /// Queries a property asynchronously based on the provided request URI and MIME type. /// @@ -211,23 +136,4 @@ public async Task> QueryResourceSetsAsync(string queryText, return property; } - - public async Task QueryPropertyValueInStringAsync(string requestUri) - { - var readerSettings = new ODataMessageReaderSettings() { BaseUri = BaseUri }; - - var uri = new Uri(BaseUri.AbsoluteUri + requestUri, UriKind.Absolute); - var requestMessage = new TestHttpClientRequestMessage(uri, Client); - - requestMessage.SetHeader("Accept", "*/*"); - - var responseMessage = await requestMessage.GetResponseAsync(); - - Assert.Equal(200, responseMessage.StatusCode); - - using (var messageReader = new ODataMessageReader(responseMessage, readerSettings, Model)) - { - return messageReader.ReadValue(EdmCoreModel.Instance.GetString(false)); - } - } }