Skip to content

Commit

Permalink
Merge pull request #179 from Kentico/feature/DEL-2330_Include_total_c…
Browse files Browse the repository at this point in the history
…ount_in_response

Support for IncludeTotalCount parameter
  • Loading branch information
tobiaskamenicky authored Nov 13, 2019
2 parents 5daf90d + f243158 commit e34cfdf
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ public void BuildWithWaitForLoadingNewContent()
Assert.True(deliveryOptions.WaitForLoadingNewContent);
}

[Fact]
public void BuildWithIncludeTotalCount()
{
var deliveryOptions = DeliveryOptionsBuilder
.CreateInstance()
.WithProjectId(Guid.NewGuid())
.UseProductionApi()
.IncludeTotalCount()
.Build();

Assert.True(deliveryOptions.IncludeTotalCount);
}

[Fact]
public void BuildWithCustomEndpointForPreviewApi()
{
Expand Down
7 changes: 4 additions & 3 deletions Kentico.Kontent.Delivery.Tests/DeliveryClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,10 @@ public async void GetTaxonomiesAsync()
public async void QueryParameters()
{

string url = $"{_baseUrl}/items?elements.personas%5Ball%5D=barista%2Ccoffee%2Cblogger&elements.personas%5Bany%5D=barista%2Ccoffee%2Cblogger&system.sitemap_locations%5Bcontains%5D=cafes&elements.product_name=Hario%20V60&elements.price%5Bgt%5D=1000&elements.price%5Bgte%5D=50&system.type%5Bin%5D=cafe%2Ccoffee&elements.price%5Blt%5D=10&elements.price%5Blte%5D=4&elements.country%5Brange%5D=Guatemala%2CNicaragua&depth=2&elements=price%2Cproduct_name&limit=10&order=elements.price%5Bdesc%5D&skip=2&language=en";
string url = $"{_baseUrl}/items?elements.personas%5Ball%5D=barista%2Ccoffee%2Cblogger&elements.personas%5Bany%5D=barista%2Ccoffee%2Cblogger&system.sitemap_locations%5Bcontains%5D=cafes&elements.product_name=Hario%20V60&elements.price%5Bgt%5D=1000&elements.price%5Bgte%5D=50&system.type%5Bin%5D=cafe%2Ccoffee&elements.price%5Blt%5D=10&elements.price%5Blte%5D=4&elements.country%5Brange%5D=Guatemala%2CNicaragua&depth=2&elements=price%2Cproduct_name&limit=10&order=elements.price%5Bdesc%5D&skip=2&language=en&includeTotalCount";
_mockHttp
.When($"{url}")
.Respond("application/json", " { 'items': [],'modular_content': {},'pagination': {'skip': 2,'limit': 10,'count': 0,'next_page': ''}}");
.Respond("application/json", " { 'items': [],'modular_content': {},'pagination': {'skip': 2,'limit': 10,'count': 0, 'total_count': 0, 'next_page': ''}}");

var client = InitializeDeliveryClientWithACustomTypeProvider(_mockHttp);

Expand All @@ -436,7 +436,8 @@ public async void QueryParameters()
new LimitParameter(10),
new OrderParameter("elements.price", SortOrder.Descending),
new SkipParameter(2),
new LanguageParameter("en")
new LanguageParameter("en"),
new IncludeTotalCountParameter()
};

var response = await client.GetItemsAsync(parameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.3.1" />
<PackageReference Include="Castle.Windsor" Version="4.1.1" />
<PackageReference Include="Castle.Windsor.MsDependencyInjection" Version="3.3.1" />
<PackageReference Include="FluentAssertions" Version="5.9.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void ExtractParameters_WhenGivenTypeWithCodenameAndExistingParams_AddsCod
{
var existingParams = new List<IQueryParameter>() { new SkipParameter(15) };

var enhancedParams = new List<IQueryParameter>(_client.ExtractParameters<TypeWithContentTypeCodename>(existingParams));
var enhancedParams = new List<IQueryParameter>(_client.EnsureContentTypeFilter<TypeWithContentTypeCodename>(existingParams));

Assert.Equal(2, enhancedParams.Count);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type={CONTENT_TYPE_CODENAME}") != null);
Expand All @@ -58,7 +58,7 @@ public void ExtractParameters_WhenGivenTypeWithCodenameAndExistingParams_AddsCod
[Fact]
public void ExtractParameters_WhenGivenTypeWithCodename_CreatesNewParams()
{
var enhancedParams = new List<IQueryParameter>(_client.ExtractParameters<TypeWithContentTypeCodename>());
var enhancedParams = new List<IQueryParameter>(_client.EnsureContentTypeFilter<TypeWithContentTypeCodename>());

Assert.Single(enhancedParams);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type={CONTENT_TYPE_CODENAME}") != null);
Expand All @@ -67,7 +67,7 @@ public void ExtractParameters_WhenGivenTypeWithCodename_CreatesNewParams()
[Fact]
public void ExtractParameters_WhenGivenTypeWithoutCodenameNoParams_CreatesEmptyParams()
{
var enhancedParams = new List<IQueryParameter>(_client.ExtractParameters<TypeWithoutContentTypeCodename>());
var enhancedParams = new List<IQueryParameter>(_client.EnsureContentTypeFilter<TypeWithoutContentTypeCodename>());

Assert.Empty(enhancedParams);
}
Expand All @@ -77,7 +77,7 @@ public void ExtractParameters_WhenGivenTypeWithoutCodenameAndParams_ReturnsParam
{
var existingParams = new List<IQueryParameter>() { new SkipParameter(15) };

var enhancedParams = new List<IQueryParameter>(_client.ExtractParameters<TypeWithoutContentTypeCodename>(existingParams));
var enhancedParams = new List<IQueryParameter>(_client.EnsureContentTypeFilter<TypeWithoutContentTypeCodename>(existingParams));

Assert.Single(enhancedParams);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type=TypeWithoutContentTypeCodename") == null);
Expand All @@ -88,7 +88,7 @@ public void ExtractParameters_WhenGivenTypeWithCodenameAndExistingTypeParameter_
{
var existingParams = new List<IQueryParameter>() { new EqualsFilter("system.type", CONTENT_TYPE_CODENAME) };

var enhancedParams = new List<IQueryParameter>(_client.ExtractParameters<TypeWithContentTypeCodename>(existingParams));
var enhancedParams = new List<IQueryParameter>(_client.EnsureContentTypeFilter<TypeWithContentTypeCodename>(existingParams));

Assert.Single(enhancedParams);
Assert.True(enhancedParams.Find(x => x.GetQueryStringParameter() == $"system.type={CONTENT_TYPE_CODENAME}") != null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using FakeItEasy;
using FluentAssertions;
using Kentico.Kontent.Delivery.Tests.Factories;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RichardSzalay.MockHttp;
using Xunit;

namespace Kentico.Kontent.Delivery.Tests.QueryParameters
{
public class IncludeTotalCountTests
{
private static readonly Guid ProjectId = Guid.NewGuid();
private static readonly string BaseUrl = $"https://deliver.kontent.ai/{ProjectId}";
private static readonly MockHttpMessageHandler MockHttp = new MockHttpMessageHandler();
private static readonly DeliveryOptions Options = new DeliveryOptions
{
ProjectId = ProjectId.ToString(),
EnableRetryPolicy = false,
IncludeTotalCount = true
};

[Fact]
public void PaginationResponse_WithoutTotalCount_DeserializedCorrectly()
{
var responsePagination = JObject.FromObject(new
{
skip = 20,
limit = 10,
count = 8,
next_page = "nextPage"
});
var expected = new Pagination(20, 10, 8, null, "nextPage");

var result = responsePagination.ToObject<Pagination>();

result.Should().BeEquivalentTo(expected);
}

[Fact]
public void PaginationResponse_WithTotalCount_DeserializedCorrectly()
{
var responsePagination = JObject.FromObject(new
{
skip = 20,
limit = 10,
count = 8,
total_count = 28,
next_page = "nextPage"
});
var expected = new Pagination(20, 10, 8, 28, "nextPage");

var result = responsePagination.ToObject<Pagination>();

result.Should().BeEquivalentTo(expected);
}

[Fact]
public async void GetItemsJson_DeliveryOptionsWithIncludeTotalCount_IncludeTotalCountParameterAdded()
{
var responseJson = JsonConvert.SerializeObject(CreateItemsResponse());
MockHttp
.Expect($"{BaseUrl}/items")
.WithExactQueryString("includeTotalCount")
.Respond("application/json", responseJson);
var client = DeliveryClientFactory.GetMockedDeliveryClientWithOptions(Options, MockHttp);

await client.GetItemsJsonAsync();

MockHttp.VerifyNoOutstandingExpectation();
}

[Fact]
public async void GetItems_DeliveryOptionsWithIncludeTotalCount_IncludeTotalCountParameterAdded()
{
var responseJson = JsonConvert.SerializeObject(CreateItemsResponse());
MockHttp
.Expect($"{BaseUrl}/items")
.WithExactQueryString("includeTotalCount")
.Respond("application/json", responseJson);
var client = DeliveryClientFactory.GetMockedDeliveryClientWithOptions(Options, MockHttp);

await client.GetItemsAsync();

MockHttp.VerifyNoOutstandingExpectation();
}

[Fact]
public async void GetItemsTyped_DeliveryOptionsWithIncludeTotalCount_IncludeTotalCountParameterAdded()
{
var responseJson = JsonConvert.SerializeObject(CreateItemsResponse());
MockHttp
.Expect($"{BaseUrl}/items")
.WithExactQueryString("system.type=cafe&includeTotalCount")
.Respond("application/json", responseJson);
var client = DeliveryClientFactory.GetMockedDeliveryClientWithOptions(Options, MockHttp);
A.CallTo(() => client.TypeProvider.GetCodename(typeof(Cafe))).Returns("cafe");

await client.GetItemsAsync<Cafe>();

MockHttp.VerifyNoOutstandingExpectation();
}

private static object CreateItemsResponse() => new
{
items = new object[0],
modular_content = new { },
pagination = new
{
skip = 0,
limit = 0,
count = 1,
total_count = 1,
next_page = ""
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class DeliveryOptionsBuilder : IDeliveryApiConfiguration, IDeliveryOption
public static IDeliveryOptionsBuilder CreateInstance()
=> new DeliveryOptionsBuilder();

private DeliveryOptionsBuilder() {}
private DeliveryOptionsBuilder() { }

IDeliveryApiConfiguration IDeliveryOptionsBuilder.WithProjectId(string projectId)
{
Expand All @@ -41,6 +41,13 @@ IOptionalDeliveryConfiguration IOptionalDeliveryConfiguration.WaitForLoadingNewC
return this;
}

IOptionalDeliveryConfiguration IOptionalDeliveryConfiguration.IncludeTotalCount()
{
_deliveryOptions.IncludeTotalCount = true;

return this;
}

IOptionalDeliveryConfiguration IOptionalDeliveryConfiguration.DisableRetryPolicy()
{
_deliveryOptions.EnableRetryPolicy = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ public interface IOptionalDeliveryConfiguration : IDeliveryOptionsBuild
/// </summary>
IOptionalDeliveryConfiguration WaitForLoadingNewContent();

/// <summary>
/// Include the total number of items matching the search criteria in the response.
/// This behavior can also be enabled for individual requests with the <see cref="IncludeTotalCountParameter"/>.
/// Please note that using this option might increase the response time.
/// </summary>
IOptionalDeliveryConfiguration IncludeTotalCount();

/// <summary>
/// Change configuration of the default retry policy.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions Kentico.Kontent.Delivery/Configuration/DeliveryOptions .cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,11 @@ public class DeliveryOptions
/// Gets or sets configuration of the default retry policy.
/// </summary>
public DefaultRetryPolicyOptions DefaultRetryPolicyOptions { get; set; } = new DefaultRetryPolicyOptions();

/// <summary>
/// Gets or sets a value that determines if the client includes the total number of items matching the search criteria in response.
/// This behavior can also be enabled for individual requests with the <see cref="IncludeTotalCountParameter"/>.
/// </summary>
public bool IncludeTotalCount { get; set; }
}
}
6 changes: 3 additions & 3 deletions Kentico.Kontent.Delivery/DeliveryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public async Task<DeliveryItemListingResponse<T>> GetItemsAsync<T>(params IQuery
/// <returns>The <see cref="DeliveryItemListingResponse{T}"/> instance that contains the content items. If no query parameters are specified, all content items are returned.</returns>
public async Task<DeliveryItemListingResponse<T>> GetItemsAsync<T>(IEnumerable<IQueryParameter> parameters)
{
var enhancedParameters = ExtractParameters<T>(parameters);
var enhancedParameters = EnsureContentTypeFilter<T>(parameters).ToList();
var endpointUrl = UrlBuilder.GetItemsUrl(enhancedParameters);
var response = await GetDeliverResponseAsync(endpointUrl);

Expand Down Expand Up @@ -261,7 +261,7 @@ public IDeliveryItemsFeed<T> GetItemsFeed<T>(params IQueryParameter[] parameters
/// <returns>The <see cref="DeliveryItemsFeed{T}"/> instance that can be used to enumerate through content items. If no query parameters are specified, all content items are enumerated.</returns>
public IDeliveryItemsFeed<T> GetItemsFeed<T>(IEnumerable<IQueryParameter> parameters)
{
var enhancedParameters = ExtractParameters<T>(parameters).ToList();
var enhancedParameters = EnsureContentTypeFilter<T>(parameters).ToList();
ValidateItemsFeedParameters(enhancedParameters);
var endpointUrl = UrlBuilder.GetItemsFeedUrl(enhancedParameters);
return new DeliveryItemsFeed<T>(GetItemsBatchAsync);
Expand Down Expand Up @@ -555,7 +555,7 @@ private bool HasStaleContent(HttpResponseMessage httpResponseMessage)
return httpResponseMessage.Headers.TryGetValues("X-Stale-Content", out var values) && values.Contains("1", StringComparer.Ordinal);
}

internal IEnumerable<IQueryParameter> ExtractParameters<T>(IEnumerable<IQueryParameter> parameters = null)
internal IEnumerable<IQueryParameter> EnsureContentTypeFilter<T>(IEnumerable<IQueryParameter> parameters = null)
{
var enhancedParameters = parameters != null
? new List<IQueryParameter>(parameters)
Expand Down
28 changes: 26 additions & 2 deletions Kentico.Kontent.Delivery/DeliveryEndpointUrlBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ public string GetItemUrl(string codename, IEnumerable<IQueryParameter> parameter

public string GetItemsUrl(string[] parameters)
{
return GetUrl(UrlTemplateItems, parameters);
var enrichedParameters = EnrichParameters(parameters).ToArray();
return GetUrl(UrlTemplateItems, enrichedParameters);
}

public string GetItemsUrl(IEnumerable<IQueryParameter> parameters)
{
return GetUrl(UrlTemplateItems, parameters);
var updatedParameters = EnrichParameters(parameters);
return GetUrl(UrlTemplateItems, updatedParameters);
}

public string GetItemsFeedUrl(string[] parameters)
Expand Down Expand Up @@ -138,5 +140,27 @@ private string AssembleHost()
var hostUrl = string.Format(endpointUrlTemplate, projectId);
return hostUrl;
}

private IEnumerable<string> EnrichParameters(IEnumerable<string> parameters)
{
var parameterList = parameters?.ToList() ?? new List<string>();
if (_deliveryOptions.IncludeTotalCount && !parameterList.Contains(new IncludeTotalCountParameter().GetQueryStringParameter()))
{
parameterList.Add(new IncludeTotalCountParameter().GetQueryStringParameter());
}

return parameterList;
}

private IEnumerable<IQueryParameter> EnrichParameters(IEnumerable<IQueryParameter> parameters)
{
var parameterList = parameters?.ToList() ?? new List<IQueryParameter>();
if (_deliveryOptions.IncludeTotalCount && !parameterList.Any(x => x is IncludeTotalCountParameter))
{
parameterList.Add(new IncludeTotalCountParameter());
}

return parameterList;
}
}
}
8 changes: 7 additions & 1 deletion Kentico.Kontent.Delivery/Models/Pagination.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public sealed class Pagination
/// </summary>
public int Count { get; }

/// <summary>
/// Gets the total number of items matching the search criteria.
/// </summary>
public int? TotalCount { get; }

/// <summary>
/// Gets the URL of the next page.
/// </summary>
Expand All @@ -32,11 +37,12 @@ public sealed class Pagination
/// Initializes a new instance of the <see cref="Pagination"/> class with information from a response.
/// </summary>
[JsonConstructor]
internal Pagination(int skip, int limit, int count, string next_page)
internal Pagination(int skip, int limit, int count, int? total_count, string next_page)
{
Skip = skip;
Limit = limit;
Count = count;
TotalCount = total_count;
NextPageUrl = next_page;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Kentico.Kontent.Delivery
{
/// <summary>
/// Specifies whether to include total count in the paging section.
/// </summary>
public sealed class IncludeTotalCountParameter : IQueryParameter
{
/// <summary>
/// Initializes a new instance of the <see cref="IncludeTotalCountParameter"/> class.
/// </summary>
public IncludeTotalCountParameter()
{
}

/// <summary>
/// Returns the query string representation of the query parameter.
/// </summary>
public string GetQueryStringParameter()
{
return "includeTotalCount";
}
}
}
Loading

0 comments on commit e34cfdf

Please sign in to comment.