Skip to content

Commit

Permalink
test(csharp): add client CTS (#2551)
Browse files Browse the repository at this point in the history
  • Loading branch information
morganleroi authored Jan 16, 2024
1 parent 3316753 commit a6d6ecd
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@ public abstract class AlgoliaConfig
private static readonly string ClientVersion =
typeof(AlgoliaConfig).GetTypeInfo().Assembly.GetName().Version.ToString();

// get the dotnet runtime version
private static readonly string DotnetVersion = Environment.Version.ToString();

/// <summary>
/// Create a new Algolia's configuration for the given credentials
/// </summary>
/// <param name="applicationId">Your application ID</param>
/// <param name="appId">Your application ID</param>
/// <param name="apiKey">Your API Key</param>
protected AlgoliaConfig(string applicationId, string apiKey)
/// <param name="client">The client name</param>
protected AlgoliaConfig(string appId, string apiKey, string client)
{
AppId = applicationId;
AppId = appId;
ApiKey = apiKey;

DefaultHeaders = new Dictionary<string, string>
{
{ Defaults.AlgoliaApplicationHeader.ToLowerInvariant(), AppId },
{ Defaults.AlgoliaApiKeyHeader.ToLowerInvariant(), ApiKey },
{ Defaults.UserAgentHeader.ToLowerInvariant(), $"Algolia For Csharp {ClientVersion}" },
{ Defaults.UserAgentHeader.ToLowerInvariant(), $"Algolia for Csharp ({ClientVersion}); {client} ({ClientVersion}); Dotnet ({DotnetVersion})" },
{ Defaults.Connection.ToLowerInvariant(), Defaults.KeepAlive },
{ Defaults.AcceptHeader.ToLowerInvariant(), JsonConfig.JsonContentType }
};
Expand Down Expand Up @@ -69,6 +73,11 @@ protected AlgoliaConfig(string applicationId, string apiKey)
/// </summary>
public TimeSpan? WriteTimeout { get; set; }

/// <summary>
/// Set the connect timeout for all requests
/// </summary>
public TimeSpan? ConnectTimeout { get; set; }

/// <summary>
/// Compression for outgoing http requests <see cref="CompressionType"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ internal class AlgoliaHttpRequester : IHttpRequester
/// Send request to the REST API
/// </summary>
/// <param name="request">Request</param>
/// <param name="totalTimeout">Timeout</param>
/// <param name="requestTimeout">Request timeout</param>
/// <param name="connectTimeout">Connect timeout</param>
/// <param name="ct">Optional cancellation token</param>
/// <returns></returns>
public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan totalTimeout,
CancellationToken ct = default)
public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan requestTimeout, TimeSpan connectTimeout,
CancellationToken ct = default)
{
if (request.Method == null)
{
Expand All @@ -57,7 +58,7 @@ public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpa
}

httpRequestMessage.Headers.Fill(request.Headers);
httpRequestMessage.SetTimeout(totalTimeout);
httpRequestMessage.SetTimeout(requestTimeout + connectTimeout);

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@ private static Dictionary<string, string> SplitQuery(string query)
return collection.AllKeys.ToDictionary(key => key, key => collection[key]);
}

public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan totalTimeout,
public async Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan requestTimeout,
TimeSpan connectTimeout,
CancellationToken ct = default)
{
EchoResponse echo = new EchoResponse();
echo.Path = request.Uri.AbsolutePath;
echo.Host = request.Uri.Host;
echo.Method = request.Method;
echo.Body = request.Body;
echo.QueryParameters = SplitQuery(request.Uri.Query);
echo.Headers = new Dictionary<string, string>(request.Headers);
EchoResponse echo = new EchoResponse
{
Path = request.Uri.AbsolutePath,
Host = request.Uri.Host,
Method = request.Method,
Body = request.Body,
QueryParameters = SplitQuery(request.Uri.Query),
Headers = new Dictionary<string, string>(request.Headers),
ConnectTimeout = connectTimeout,
ResponseTimeout = requestTimeout
};

LastResponse = echo;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class EchoResponse
public String Body;
public Dictionary<string, string> QueryParameters;
public Dictionary<string, string> Headers;
public int ConnectTimeout;
public int ResponseTimeout;
public TimeSpan ConnectTimeout;
public TimeSpan ResponseTimeout;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ public interface IHttpRequester
/// Sends the HTTP request
/// </summary>
/// <param name="request">Request object</param>
/// <param name="totalTimeout">Timeout</param>
/// <param name="requestTimeout">Request timeout</param>
/// <param name="connectTimeout">Connect timeout</param>
/// <param name="ct">Optional cancellation token</param>
/// <returns></returns>
Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan totalTimeout,
CancellationToken ct = default);
Task<AlgoliaHttpResponse> SendRequestAsync(Request request, TimeSpan requestTimeout, TimeSpan connectTimeout, CancellationToken ct = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ private async Task<TResult> ExecuteRequestAsync<TResult, TData>(HttpMethod metho
}

AlgoliaHttpResponse response = await _httpClient
.SendRequestAsync(request, requestTimeout, ct)
.SendRequestAsync(request, requestTimeout, _algoliaConfig.ConnectTimeout ?? Defaults.ConnectTimeout, ct)
.ConfigureAwait(false);

_errorMessage = response.Error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ internal class Defaults
/// </summary>
public static TimeSpan WriteTimeout = TimeSpan.FromSeconds(30);

/// <summary>
/// Connect timeout
/// </summary>
public static TimeSpan ConnectTimeout = TimeSpan.FromSeconds(2);

public const string AcceptHeader = "Accept";
public const string AlgoliaApplicationHeader = "X-Algolia-Application-Id";
public const string AlgoliaApiKeyHeader = "X-Algolia-API-Key";
Expand Down
34 changes: 21 additions & 13 deletions templates/csharp/Configuration.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ namespace Algolia.Search.Clients
/// The configuration of the {{packageName}} client
/// A client should have it's own configuration ie on configuration per client instance
/// </summary>
/// <param name="applicationId">Your application ID</param>
/// <param name="appId">Your application ID</param>
/// <param name="apiKey">Your API Key</param>
/// <param name="region">Targeted region {{#fallbackToAliasHost}}(optional){{/fallbackToAliasHost}}</param>
public {{packageName}}Config(string applicationId, string apiKey, string region{{#fallbackToAliasHost}} = null{{/fallbackToAliasHost}}) : base(applicationId, apiKey)
public {{packageName}}Config(string appId, string apiKey, string region{{#fallbackToAliasHost}} = null{{/fallbackToAliasHost}}) : base(appId, apiKey, "{{packageName}}")
{
DefaultHosts = GetDefaultHosts(region);
Compression = CompressionType.NONE;
Expand All @@ -32,51 +32,51 @@ namespace Algolia.Search.Clients
/// The configuration of the {{packageName}} client
/// A client should have it's own configuration ie on configuration per client instance
/// </summary>
/// <param name="applicationId">Your application ID</param>
/// <param name="appId">Your application ID</param>
/// <param name="apiKey">Your API Key</param>
public {{packageName}}Config(string applicationId, string apiKey) : base(applicationId, apiKey)
public {{packageName}}Config(string appId, string apiKey) : base(appId, apiKey, "{{packageName}}")
{
DefaultHosts = GetDefaultHosts(applicationId);
DefaultHosts = GetDefaultHosts(appId);
Compression = CompressionType.NONE;
}
{{/hasRegionalHost}}
{{^hasRegionalHost}}
private static List<StatefulHost> GetDefaultHosts(string applicationId)
private static List<StatefulHost> GetDefaultHosts(string appId)
{
List<StatefulHost> hosts = new List<StatefulHost>
{
new StatefulHost
{
Url = $"{applicationId}-dsn.algolia.net",
Url = $"{appId}-dsn.algolia.net",
Up = true,
LastUse = DateTime.UtcNow,
Accept = CallType.Read
},
new StatefulHost
{
Url = $"{applicationId}.algolia.net", Up = true, LastUse = DateTime.UtcNow, Accept = CallType.Write,
Url = $"{appId}.algolia.net", Up = true, LastUse = DateTime.UtcNow, Accept = CallType.Write,
}
};

var commonHosts = new List<StatefulHost>
{
new StatefulHost
{
Url = $"{applicationId}-1.algolianet.com",
Url = $"{appId}-1.algolianet.com",
Up = true,
LastUse = DateTime.UtcNow,
Accept = CallType.Read | CallType.Write,
},
new StatefulHost
{
Url = $"{applicationId}-2.algolianet.com",
Url = $"{appId}-2.algolianet.com",
Up = true,
LastUse = DateTime.UtcNow,
Accept = CallType.Read | CallType.Write,
},
new StatefulHost
{
Url = $"{applicationId}-3.algolianet.com",
Url = $"{appId}-3.algolianet.com",
Up = true,
LastUse = DateTime.UtcNow,
Accept = CallType.Read | CallType.Write,
Expand All @@ -91,10 +91,18 @@ namespace Algolia.Search.Clients
private static List<StatefulHost> GetDefaultHosts(string region)
{
var regions = new List<string> { {{#allowedRegions}}"{{.}}"{{^-last}},{{/-last}}{{/allowedRegions}} };
if (region != null && !regions.Contains(region))
{{^fallbackToAliasHost}}
if (region == null || !regions.Contains(region))
{
throw new ArgumentException($"`region` must be one of the following {regions}");
throw new ArgumentException($"`region` is required and must be one of the following: {string.Join(", ", regions)}");
}
{{/fallbackToAliasHost}}
{{#fallbackToAliasHost}}
if(region != null && !regions.Contains(region))
{
throw new ArgumentException($"`region` must be one of the following: {string.Join(", ", regions)}");
}
{{/fallbackToAliasHost}}

var selectedRegion = {{#fallbackToAliasHost}}region == null ? "{{{hostWithFallback}}}" : {{/fallbackToAliasHost}} "{{{regionalHost}}}".Replace("{region}", region);

Expand Down
11 changes: 5 additions & 6 deletions templates/csharp/libraries/httpclient/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,19 @@ namespace Algolia.Search.Clients
{
if (httpRequester == null)
{
throw new ArgumentNullException(nameof(httpRequester), "An httpRequester is required");
throw new ArgumentException("An httpRequester is required");
}
if (config == null)
{
throw new ArgumentNullException(nameof(config), "A config is required");
throw new ArgumentException("A config is required");
}
if (string.IsNullOrWhiteSpace(config.AppId))
{
throw new ArgumentNullException(nameof(config.AppId), "Application ID is required");
throw new ArgumentException("`AppId` is missing.");
}
if (string.IsNullOrWhiteSpace(config.ApiKey))
{
throw new ArgumentNullException(nameof(config.ApiKey), "An API key is required");
throw new ArgumentException("`ApiKey` is missing.");
}

_config = config;
Expand Down Expand Up @@ -120,8 +120,7 @@ namespace Algolia.Search.Clients
{{#required}}
{{^vendorExtensions.x-csharp-value-type}}
if ({{paramName}} == null)
throw new ApiException(400, "Missing required parameter '{{paramName}}' when calling {{classname}}->{{operationId}}");

throw new ApiException(400, "Parameter `{{paramName}}` is required when calling `{{operationId}}`.");
{{/vendorExtensions.x-csharp-value-type}}
{{/required}}
{{/allParams}}
Expand Down
1 change: 1 addition & 0 deletions templates/csharp/tests/client/createClient.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{^autoCreateClient}}var client = {{/autoCreateClient}}new {{client}}(new {{clientPrefix}}Config("{{parametersWithDataTypeMap.appId.value}}","{{parametersWithDataTypeMap.apiKey.value}}"{{#hasRegionalHost}}{{#parametersWithDataTypeMap.region}},"{{parametersWithDataTypeMap.region.value}}"{{/parametersWithDataTypeMap.region}}{{/hasRegionalHost}}), _echo);
9 changes: 9 additions & 0 deletions templates/csharp/tests/client/method.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
await client.{{#lambda.pascalcase}}{{#path}}.{{.}}{{/path}}{{/lambda.pascalcase}}Async{{#isGeneric}}<Object>{{/isGeneric}}({{#parametersWithDataType}}{{> tests/generateParams}}{{^-last}},{{/-last}}{{/parametersWithDataType}}{{#hasRequestOptions}}, new RequestOptions(){
{{#requestOptions.queryParameters.parametersWithDataType}}
QueryParameters = new Dictionary<string, object>(){ {"{{{key}}}", {{> tests/requests/requestOptionsParams}} }},
{{/requestOptions.queryParameters.parametersWithDataType}}
{{#requestOptions.headers.parametersWithDataType}}
Headers = new Dictionary<string, string>(){ {"{{{key}}}", "{{{value}}}" }},
{{/requestOptions.headers.parametersWithDataType}}
}{{/hasRequestOptions}});
EchoResponse result = _echo.LastResponse;
6 changes: 6 additions & 0 deletions templates/csharp/tests/client/step.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{{#isCreateClient}}
{{> tests/client/createClient}}
{{/isCreateClient}}
{{#isMethod}}
{{> tests/client/method}}
{{/isMethod}}
57 changes: 57 additions & 0 deletions templates/csharp/tests/client/suite.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Algolia.Search.Clients;
using Algolia.Search.Models.{{clientPrefix}};
using Algolia.Search.Http;
using System.Text.RegularExpressions;
using Xunit;

public class {{client}}Tests
{
private readonly EchoHttpRequester _echo;
private Exception _ex;
public {{client}}Tests()
{
_echo = new EchoHttpRequester();
}

[Fact]
public void Dispose()
{
}

{{#blocksClient}}
{{#tests}}
[Fact(DisplayName = "{{{testName}}}")]
public async Task {{#lambda.pascalcase}}{{testType}}Test{{testIndex}}{{/lambda.pascalcase}}()
{
{{#autoCreateClient}}
var client = new {{client}}(new {{clientPrefix}}Config("appId", "apiKey"{{#hasRegionalHost}},"{{defaultRegion}}"{{/hasRegionalHost}}), _echo);
{{/autoCreateClient}}
{{#steps}}
{{#isError}}
_ex = await Assert.ThrowsAnyAsync<Exception>(async () => { {{> tests/client/step}} });
Assert.Equal("{{{expectedError}}}".ToLowerInvariant(), _ex.Message.ToLowerInvariant());

{{/isError}}
{{^isError}}
{{> tests/client/step}}
{{#match}}
{{#testUserAgent}} {
var regexp = new Regex("{{#lambda.escapeSlash}}{{{match}}}{{/lambda.escapeSlash}}");
Assert.Matches(regexp,result.Headers["user-agent"]);
}{{/testUserAgent}}
{{#testTimeouts}}
Assert.Equal({{{match.parametersWithDataTypeMap.connectTimeout.value}}}, result.ConnectTimeout.TotalMilliseconds);
Assert.Equal({{{match.parametersWithDataTypeMap.responseTimeout.value}}}, result.ResponseTimeout.TotalMilliseconds);
{{/testTimeouts}}
{{#testHost}}
Assert.Equal("{{{match}}}", result.Host);
{{/testHost}}
{{/match}}
{{/isError}}
{{/steps}}
}
{{/tests}}
{{/blocksClient}}
}

0 comments on commit a6d6ecd

Please sign in to comment.