diff --git a/src/CommunityToolkit.Datasync.Client/Service/DatasyncServiceClient.cs b/src/CommunityToolkit.Datasync.Client/Service/DatasyncServiceClient.cs index a80513f..86197c7 100644 --- a/src/CommunityToolkit.Datasync.Client/Service/DatasyncServiceClient.cs +++ b/src/CommunityToolkit.Datasync.Client/Service/DatasyncServiceClient.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using CommunityToolkit.Datasync.Client.Http; using CommunityToolkit.Datasync.Client.Paging; using CommunityToolkit.Datasync.Client.Query; using CommunityToolkit.Datasync.Client.Query.Linq; @@ -23,6 +24,31 @@ namespace CommunityToolkit.Datasync.Client; /// The type of entity being processed by this service client. internal class DatasyncServiceClient : IDatasyncServiceClient where TEntity : class { + /// + /// Creates a new using default information based on + /// the provided. + /// + /// + /// The default path is /tables/entityName as a relative URI to the Endpoint in the options. + /// + /// The to use. + public DatasyncServiceClient(HttpClientOptions options) + : this(new Uri($"/tables/{typeof(TEntity).Name.ToLowerInvariant()}", UriKind.Relative), new HttpClientFactory(options).CreateClient()) + { + } + + /// + /// Creates a new with the normal information required for + /// communicating with a datasync service, using the default JSON Serializer Options. + /// + /// The endpoint of the table controller that processes the entity. + /// The to use for communication. + /// Thrown if the endpoint is not valid. + public DatasyncServiceClient(Uri endpoint, HttpClient client) + : this(endpoint, client, DatasyncSerializer.JsonSerializerOptions) + { + } + /// /// Creates a new with the normal information required for /// communicating with a datasync service. @@ -33,6 +59,7 @@ internal class DatasyncServiceClient : IDatasyncServiceClient /// Thrown if the endpoint is not valid. public DatasyncServiceClient(Uri endpoint, HttpClient client, JsonSerializerOptions serializerOptions) { + endpoint = MakeAbsoluteUri(client.BaseAddress, endpoint); ThrowIf.IsNotValidEndpoint(endpoint, nameof(endpoint)); ArgumentNullException.ThrowIfNull(client, nameof(client)); ArgumentNullException.ThrowIfNull(serializerOptions, nameof(serializerOptions)); @@ -493,4 +520,28 @@ internal async ValueTask> GetNextPageAsync(string queryOrContinuat result.ThrowIfNotSuccessful(requireContent: true); return result.Value!; } + + /// + /// Converts a base address + relative/absolute URI into the appropriate URI for the datasync service. + /// + /// The base address from the client. + /// A relative or absolute URI + /// + internal static Uri MakeAbsoluteUri(Uri? baseAddress, Uri relativeOrAbsoluteUri) + { + if (relativeOrAbsoluteUri.IsAbsoluteUri) + { + return new Uri($"{relativeOrAbsoluteUri.ToString().TrimEnd('/')}/"); + } + + if (baseAddress != null) + { + if (baseAddress.IsAbsoluteUri) + { + return new Uri($"{new Uri(baseAddress, relativeOrAbsoluteUri).ToString().TrimEnd('/')}/"); + } + } + + throw new UriFormatException("Invalid combination of baseAddress and relativeUri"); + } } diff --git a/tests/CommunityToolkit.Datasync.Client.Test/Service/DatasyncServiceClient_Tests.cs b/tests/CommunityToolkit.Datasync.Client.Test/Service/DatasyncServiceClient_Tests.cs index 917f081..088bf5d 100644 --- a/tests/CommunityToolkit.Datasync.Client.Test/Service/DatasyncServiceClient_Tests.cs +++ b/tests/CommunityToolkit.Datasync.Client.Test/Service/DatasyncServiceClient_Tests.cs @@ -9,6 +9,7 @@ #pragma warning disable IDE0028 // Simplify collection initialization using CommunityToolkit.Datasync.Client.Http; +using CommunityToolkit.Datasync.Client.Offline.Operations; using CommunityToolkit.Datasync.Client.Serialization; using CommunityToolkit.Datasync.Client.Test.Helpers; using CommunityToolkit.Datasync.TestCommon; @@ -151,6 +152,39 @@ private Page CreatePage(int count, long? totalCount = null, s } #endregion + #region Ctors + [Fact] + public void Ctor_WithOptions() + { + JsonSerializerOptions serializerOptions = DatasyncSerializer.JsonSerializerOptions; + + HttpClientOptions options = new() + { + Endpoint = new Uri("http://localhost"), + HttpPipeline = [this.mockHandler] + }; + + DatasyncServiceClient serviceClient = new(options); + + serviceClient.Endpoint.ToString().Should().Be("http://localhost/tables/clientmovie/"); + serviceClient.Client.Should().NotBeNull(); + serviceClient.JsonSerializerOptions.Should().BeSameAs(serializerOptions); + } + + [Fact] + public void Ctor_WithoutSerializer() + { + JsonSerializerOptions serializerOptions = DatasyncSerializer.JsonSerializerOptions; + HttpClient client = new() { BaseAddress = new Uri("http://localhost/") }; + Uri tableUri = new("/tables/movies", UriKind.Relative); + DatasyncServiceClient serviceClient = new(tableUri, client); + + serviceClient.Endpoint.ToString().Should().Be("http://localhost/tables/movies/"); + serviceClient.Client.Should().BeSameAs(client); + serviceClient.JsonSerializerOptions.Should().BeSameAs(serializerOptions); + } + #endregion + #region AddAsync [Fact] public async Task AddAsync_Throws_On_Null() @@ -189,7 +223,7 @@ public async Task AddAsync_Success() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Post); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); (await request.Content.ReadAsStringAsync()).Should().Be(expected); response.Should().NotBeNull(); @@ -214,7 +248,7 @@ public async Task AddAsync_Success_Extn() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Post); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); (await request.Content.ReadAsStringAsync()).Should().Be(expected); response.Should().NotBeNull(); @@ -243,7 +277,7 @@ public async Task AddAsync_Conflict(HttpStatusCode code) HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Post); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); (await request.Content.ReadAsStringAsync()).Should().Be(expected); ex.ClientEntity.Should().BeEquivalentTo(entity, this.entityEquivalentOptions); @@ -327,7 +361,7 @@ public async Task CountAsync_Success_NoQuery() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -350,7 +384,7 @@ public async Task CountAsync_Success_WithQuery() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -382,7 +416,7 @@ public async Task CountAsync_Success_WithQuery_Extension() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -404,7 +438,7 @@ public async Task CountAsync_Success_NoQuery_Extension() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -427,7 +461,7 @@ public async Task CountAsync_Success_SkipTopSelect() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -665,7 +699,7 @@ public async Task GetPageAsync_Throws_On_Null() [InlineData("$filter=booleanValue")] public async Task GetPageAsync_Success_ItemsOnly(string query) { - string expectedUri = string.IsNullOrEmpty(query) ? "http://localhost/tables/kitchensink" : $"http://localhost/tables/kitchensink?{query}"; + string expectedUri = string.IsNullOrEmpty(query) ? "http://localhost/tables/kitchensink/" : $"http://localhost/tables/kitchensink/?{query}"; Page page = CreatePage(5); DatasyncServiceClient client = GetMockClient(); ServiceResponse> response = await client.GetPageAsync(query, new DatasyncServiceOptions()); @@ -696,7 +730,7 @@ public async Task GetPageAsync_Success_ItemsOnly(string query) [InlineData("$filter=booleanValue")] public async Task GetPageAsync_Success_Items_TotalCount(string query) { - string expectedUri = string.IsNullOrEmpty(query) ? "http://localhost/tables/kitchensink" : $"http://localhost/tables/kitchensink?{query}"; + string expectedUri = string.IsNullOrEmpty(query) ? "http://localhost/tables/kitchensink/" : $"http://localhost/tables/kitchensink/?{query}"; Page page = CreatePage(5, 20L); DatasyncServiceClient client = GetMockClient(); ServiceResponse> response = await client.GetPageAsync(query, new DatasyncServiceOptions()); @@ -727,7 +761,7 @@ public async Task GetPageAsync_Success_Items_TotalCount(string query) [InlineData("$filter=booleanValue")] public async Task GetPageAsync_Success_Items_NextLink(string query) { - string expectedUri = string.IsNullOrEmpty(query) ? "http://localhost/tables/kitchensink" : $"http://localhost/tables/kitchensink?{query}"; + string expectedUri = string.IsNullOrEmpty(query) ? "http://localhost/tables/kitchensink/" : $"http://localhost/tables/kitchensink/?{query}"; Page page = CreatePage(5, null, "$filter=booleanValue&$skip=5"); DatasyncServiceClient client = GetMockClient(); ServiceResponse> response = await client.GetPageAsync(query, new DatasyncServiceOptions()); @@ -867,7 +901,7 @@ public async Task LongCountAsync_Success_NoQuery() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -890,7 +924,7 @@ public async Task LongCountAsync_Success_WithQuery() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -922,7 +956,7 @@ public async Task LongCountAsync_Success_WithQuery_Extn() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -944,7 +978,7 @@ public async Task LongCountAsync_Success_NoQuery_Extn() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -967,7 +1001,7 @@ public async Task LongCountAsync_Success_SkipTopSelect() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$top=0&$count=true"); response.Should().NotBeNull(); response.HasContent.Should().BeTrue(); @@ -1149,7 +1183,7 @@ public async Task Query_NoItems() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -1165,7 +1199,7 @@ public async Task Query_OnePageOfItems() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -1187,12 +1221,12 @@ public async Task Query_TwoPagesOfItems() HttpRequestMessage page1Request = this.mockHandler.Requests[0]; page1Request.Should().NotBeNull(); page1Request.Method.Should().Be(HttpMethod.Get); - page1Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + page1Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); HttpRequestMessage page2Request = this.mockHandler.Requests[1]; page2Request.Should().NotBeNull(); page2Request.Method.Should().Be(HttpMethod.Get); - page2Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$skip=5"); + page2Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$skip=5"); } [Fact] @@ -1216,17 +1250,17 @@ public async Task Query_ThreePagesOfItems() HttpRequestMessage page1Request = this.mockHandler.Requests[0]; page1Request.Should().NotBeNull(); page1Request.Method.Should().Be(HttpMethod.Get); - page1Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + page1Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); HttpRequestMessage page2Request = this.mockHandler.Requests[1]; page2Request.Should().NotBeNull(); page2Request.Method.Should().Be(HttpMethod.Get); - page2Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$skip=5"); + page2Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$skip=5"); HttpRequestMessage page3Request = this.mockHandler.Requests[2]; page3Request.Should().NotBeNull(); page3Request.Method.Should().Be(HttpMethod.Get); - page3Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$skip=10"); + page3Request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$skip=10"); } [Fact] @@ -1253,7 +1287,7 @@ public async Task Query_RequestsSimpleFilter() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } [Fact] @@ -1269,7 +1303,7 @@ public async Task Query_RequestsComplexFilter() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29&$orderby=guidValue,intValue desc&$skip=5&$top=100"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29&$orderby=guidValue,intValue desc&$skip=5&$top=100"); } #endregion @@ -1986,7 +2020,7 @@ public async Task ToArrayAsync_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2001,7 +2035,7 @@ public async Task ToArrayAsync_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } #endregion @@ -2019,7 +2053,7 @@ public async Task ToAsyncEnumerable_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2035,7 +2069,7 @@ public async Task ToAsyncEnumerable_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } #endregion @@ -2054,7 +2088,7 @@ public async Task ToAsyncPageable_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2071,7 +2105,7 @@ public async Task ToAsyncPageable_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } #endregion @@ -2089,7 +2123,7 @@ public async Task ToDictionaryAsync_FirstForm_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2105,7 +2139,7 @@ public async Task ToDictionaryAsync_SecondForm_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2121,7 +2155,7 @@ public async Task ToDictionaryAsync_FirstForm_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } [Fact] @@ -2137,7 +2171,7 @@ public async Task ToDictionaryAsync_SecondForm_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } #endregion @@ -2155,7 +2189,7 @@ public async Task ToHashSetAsync_FirstForm_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2173,7 +2207,7 @@ public async Task ToHashSetAsync_SecondForm_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2189,7 +2223,7 @@ public async Task ToHashSetAsync_FirstForm_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } [Fact] @@ -2207,7 +2241,7 @@ public async Task ToHashSetAsync_SecondForm_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } class ClientKitchenSinkComparer : IEqualityComparer @@ -2233,7 +2267,7 @@ public async Task ToListAsync_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2248,7 +2282,7 @@ public async Task ToListAsync_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } #endregion @@ -2265,7 +2299,7 @@ public async Task ToObservableCollectionAsync_NewCollection_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2281,7 +2315,7 @@ public async Task ToObservableCollectionAsync_ExistingCollection_Table() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/"); } [Fact] @@ -2296,7 +2330,7 @@ public async Task ToObservableCollectionAsync_NewCollection_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } [Fact] @@ -2312,7 +2346,7 @@ public async Task ToObservableCollectionAsync_ExistingCollection_Query() HttpRequestMessage request = this.mockHandler.Requests.SingleOrDefault(); request.Should().NotBeNull(); request.Method.Should().Be(HttpMethod.Get); - request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink?$filter=%28stringValue eq %27abc%27%29"); + request.RequestUri.ToString().Should().Be("http://localhost/tables/kitchensink/?$filter=%28stringValue eq %27abc%27%29"); } #endregion @@ -3918,6 +3952,38 @@ public void GetCountOrThrow_NullValue_Throws() } #endregion + #region MakeAbsoluteUri + [Theory] + [InlineData(null, "https://test.zumo.com/tables/movies", "https://test.zumo.com/tables/movies/")] + [InlineData(null, "https://test.zumo.com/tables/movies/", "https://test.zumo.com/tables/movies/")] + [InlineData("https://test.zumo.com", "/tables/movies", "https://test.zumo.com/tables/movies/")] + [InlineData("https://test.zumo.com", "/tables/movies/", "https://test.zumo.com/tables/movies/")] + [InlineData("https://test.zumo.com/", "/tables/movies", "https://test.zumo.com/tables/movies/")] + [InlineData("https://test.zumo.com/", "/tables/movies/", "https://test.zumo.com/tables/movies/")] + [InlineData("https://test.zumo.com/tables", "movies", "https://test.zumo.com/movies/")] + [InlineData("https://test.zumo.com/tables", "movies/", "https://test.zumo.com/movies/")] + [InlineData("https://test.zumo.com/tables", "/api/movies", "https://test.zumo.com/api/movies/")] + [InlineData("https://test.zumo.com/tables", "/api/movies/", "https://test.zumo.com/api/movies/")] + public void MakeAbsoluteUri_Works(string ba, string bb, string expected) + { + Uri arg1 = string.IsNullOrEmpty(ba) ? null : new Uri(ba, UriKind.Absolute); + Uri arg2 = bb.StartsWith("http") ? new Uri(bb, UriKind.Absolute) : new Uri(bb, UriKind.Relative); + Uri actual = DatasyncServiceClient.MakeAbsoluteUri(arg1, arg2); + + actual.ToString().Should().Be(expected); + } + + [Fact] + public void MakeAbsoluteUri_BaseAddressRelative() + { + Uri arg1 = new("tables/movies", UriKind.Relative); + Uri arg2 = new("tables/movies", UriKind.Relative); + + Action act = () => DatasyncServiceClient.MakeAbsoluteUri(arg1, arg2); + act.Should().Throw(); + } + #endregion + #region Tear down protected virtual void Dispose(bool disposing) {