Skip to content

[Backport 8.2] Make ElasticsearchClient more mockable #6954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
</PropertyGroup>
<PropertyGroup Condition="$(IsPackable) == True">
<OutDir>bin/$(Configuration)/$(TargetFramework)/</OutDir>
<NoWarn>1591,1572,1571,1573,1587,1570,NU5048,</NoWarn>
<NoWarn>1591,1572,1571,1573,1587,1570,NU5048</NoWarn>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<Prefer32Bit>false</Prefer32Bit>
<DebugSymbols>true</DebugSymbols>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ namespace Elastic.Clients.Elasticsearch;

public partial class ElasticsearchClient
{
public BulkAllObservable<T> BulkAll<T>(IEnumerable<T> documents, Action<BulkAllRequestDescriptor<T>> configure, CancellationToken cancellationToken = default)
public virtual BulkAllObservable<T> BulkAll<T>(IEnumerable<T> documents, Action<BulkAllRequestDescriptor<T>> configure, CancellationToken cancellationToken = default)
{
var descriptor = new BulkAllRequestDescriptor<T>(documents);
configure?.Invoke(descriptor);
return BulkAll<T>(descriptor, cancellationToken);
}

public BulkAllObservable<T> BulkAll<T>(IBulkAllRequest<T> request, CancellationToken cancellationToken = default) =>
public virtual BulkAllObservable<T> BulkAll<T>(IBulkAllRequest<T> request, CancellationToken cancellationToken = default) =>
new(this, request, cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@ namespace Elastic.Clients.Elasticsearch;

public partial class ElasticsearchClient
{
public Task<UpdateResponse<TDocument>> UpdateAsync<TDocument, TPartialDocument>(IndexName index, Id id, CancellationToken cancellationToken = default)
public virtual Task<UpdateResponse<TDocument>> UpdateAsync<TDocument, TPartialDocument>(IndexName index, Id id, CancellationToken cancellationToken = default)
{
var descriptor = new UpdateRequestDescriptor<TDocument, TPartialDocument>(index, id);
return DoRequestAsync<UpdateRequestDescriptor<TDocument, TPartialDocument>, UpdateResponse<TDocument>, UpdateRequestParameters>(descriptor);
return DoRequestAsync<UpdateRequestDescriptor<TDocument, TPartialDocument>, UpdateResponse<TDocument>, UpdateRequestParameters>(descriptor, cancellationToken);
}

public Task<UpdateResponse<TDocument>> UpdateAsync<TDocument, TPartialDocument>(IndexName index, Id id, Action<UpdateRequestDescriptor<TDocument, TPartialDocument>> configureRequest, CancellationToken cancellationToken = default)
public virtual Task<UpdateResponse<TDocument>> UpdateAsync<TDocument, TPartialDocument>(IndexName index, Id id, Action<UpdateRequestDescriptor<TDocument, TPartialDocument>> configureRequest, CancellationToken cancellationToken = default)
{
var descriptor = new UpdateRequestDescriptor<TDocument, TPartialDocument>(index, id);
configureRequest?.Invoke(descriptor);
return DoRequestAsync<UpdateRequestDescriptor<TDocument, TPartialDocument>, UpdateResponse<TDocument>, UpdateRequestParameters>(descriptor);
return DoRequestAsync<UpdateRequestDescriptor<TDocument, TPartialDocument>, UpdateResponse<TDocument>, UpdateRequestParameters>(descriptor, cancellationToken);
}

public UpdateResponse<TDocument> Update<TDocument, TPartialDocument>(IndexName index, Id id)
public virtual UpdateResponse<TDocument> Update<TDocument, TPartialDocument>(IndexName index, Id id)
{
var descriptor = new UpdateRequestDescriptor<TDocument, TPartialDocument>(index, id);
return DoRequest<UpdateRequestDescriptor<TDocument, TPartialDocument>, UpdateResponse<TDocument>, UpdateRequestParameters>(descriptor);
}

public UpdateResponse<TDocument> Update<TDocument, TPartialDocument>(IndexName index, Id id, Action<UpdateRequestDescriptor<TDocument, TPartialDocument>> configureRequest)
public virtual UpdateResponse<TDocument> Update<TDocument, TPartialDocument>(IndexName index, Id id, Action<UpdateRequestDescriptor<TDocument, TPartialDocument>> configureRequest)
{
var descriptor = new UpdateRequestDescriptor<TDocument, TPartialDocument>(index, id);
configureRequest?.Invoke(descriptor);
Expand Down
35 changes: 15 additions & 20 deletions src/Elastic.Clients.Elasticsearch/Client/ElasticsearchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,39 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Elastic.Clients.Elasticsearch.IndexManagement;
using Elastic.Clients.Elasticsearch.Requests;
using Elastic.Transport;
using Elastic.Transport.Products.Elasticsearch;

namespace Elastic.Clients.Elasticsearch;

/// <inheritdoc />
public sealed partial class ElasticsearchClient
/// <summary>
/// A strongly-typed client for communicating with Elasticsearch server endpoints.
/// </summary>
public partial class ElasticsearchClient
{
private readonly HttpTransport<IElasticsearchClientSettings> _transport;

internal static ConditionalWeakTable<JsonSerializerOptions, IElasticsearchClientSettings> SettingsTable { get; } = new();

/// <summary>
/// Creates a client configured to connect to localhost:9200.
/// Creates a client configured to connect to http://localhost:9200.
/// </summary>
public ElasticsearchClient() : this(new ElasticsearchClientSettings(new Uri("http://localhost:9200"))) { }

/// <summary>
/// Creates a client configured to connect to a node reachable at the provided <paramref name="uri" />.
/// Creates a client configured to connect to a node reachable at the provided <paramref name="uri" />.
/// </summary>
/// <param name="uri">The <see cref="Uri" /> to connect to.</param>
public ElasticsearchClient(Uri uri) : this(new ElasticsearchClientSettings(uri)) { }

/// <summary>
/// Creates a client configured to communicate with Elastic Cloud using the provided <paramref name="cloudId" />.
/// <para>See the <see cref="CloudNodePool" /> documentation for more information on how to obtain your Cloud Id.</para>
/// <para>
/// If you want more control, use the <see cref="ElasticsearchClient(IElasticsearchClientSettings)" /> constructor and
/// pass
/// an instance of
/// <see cref="ElasticsearchClientSettings" /> that takes a <paramref name="cloudId" /> in its constructor as well.
/// </para>
/// Creates a client configured to communicate with Elastic Cloud using the provided <paramref name="cloudId" />.
/// <para>See the <see cref="CloudNodePool" /> documentation for more information on how to obtain your Cloud Id.</para>
/// <para>
/// If you want more control, use the <see cref="ElasticsearchClient(IElasticsearchClientSettings)" /> constructor and
/// pass an instance of <see cref="ElasticsearchClientSettings" /> that takes a <paramref name="cloudId" /> in its constructor as well.
/// </para>
/// </summary>
/// <param name="cloudId">The Cloud ID of an Elastic Cloud deployment.</param>
/// <param name="credentials">The credentials to use for the connection.</param>
Expand All @@ -51,19 +50,15 @@ public ElasticsearchClient(string cloudId, AuthorizationHeader credentials) : th
}

/// <summary>
/// TODO
/// Creates a client using the provided configuration to initialise the client.
/// </summary>
/// <param name="elasticsearchClientSettings"></param>
/// <param name="elasticsearchClientSettings">The <see cref="IElasticsearchClientSettings"/> used to configure the client.</param>
public ElasticsearchClient(IElasticsearchClientSettings elasticsearchClientSettings)
: this(new DefaultHttpTransport<IElasticsearchClientSettings>(elasticsearchClientSettings))
{
}

/// <summary>
/// TODO
/// </summary>
/// <param name="transport"></param>
public ElasticsearchClient(HttpTransport<IElasticsearchClientSettings> transport)
internal ElasticsearchClient(HttpTransport<IElasticsearchClientSettings> transport)
{
transport.ThrowIfNull(nameof(transport));
transport.Settings.ThrowIfNull(nameof(transport.Settings));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Elastic.Clients.Elasticsearch.IndexManagement;

public partial class IndicesNamespace
public partial class IndicesNamespacedClient
{
/// <summary>
/// Refresh one or more indices. A refresh makes recent operations performed on one or more indices available for search. For data streams,
Expand Down
60 changes: 44 additions & 16 deletions src/Elastic.Clients.Elasticsearch/Client/NamespacedClientProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@

namespace Elastic.Clients.Elasticsearch;

/// <summary>
/// </summary>
/// <remarks>
/// Not intended to be used directly.
/// </remarks>
public abstract class NamespacedClientProxy
{
private const string InvalidOperation = "The client has not been initialised for proper usage as may have been partially mocked. Ensure you are using a " +
"new instance of ElasticsearchClient to perform requests over a network to Elasticsearch.";

private readonly ElasticsearchClient _client;

/// <summary>
/// Initializes a new instance for mocking.
/// </summary>
protected NamespacedClientProxy() { }

internal NamespacedClientProxy(ElasticsearchClient client) => _client = client;

Expand All @@ -28,33 +31,53 @@ internal TResponse DoRequest<TRequest, TResponse, TRequestParameters>(
Action<IRequestConfiguration>? forceConfiguration = null)
where TRequest : Request<TRequestParameters>
where TResponse : ElasticsearchResponse, new()
where TRequestParameters : RequestParameters, new() =>
_client.DoRequest<TRequest, TResponse, TRequestParameters>(request, parameters, forceConfiguration);
where TRequestParameters : RequestParameters, new()
{
if (_client is null)
ThrowHelper.ThrowInvalidOperationException(InvalidOperation);

return _client.DoRequest<TRequest, TResponse, TRequestParameters>(request, parameters, forceConfiguration);
}

internal TResponse DoRequest<TRequest, TResponse, TRequestParameters>(
TRequest request,
Action<IRequestConfiguration>? forceConfiguration = null)
where TRequest : Request<TRequestParameters>
where TResponse : ElasticsearchResponse, new()
where TRequestParameters : RequestParameters, new() =>
_client.DoRequest<TRequest, TResponse, TRequestParameters>(request, forceConfiguration);
where TRequestParameters : RequestParameters, new()
{
if (_client is null)
ThrowHelper.ThrowInvalidOperationException(InvalidOperation);

return _client.DoRequest<TRequest, TResponse, TRequestParameters>(request, forceConfiguration);
}

internal Task<TResponse> DoRequestAsync<TRequest, TResponse, TRequestParameters>(
TRequest request,
TRequestParameters parameters,
CancellationToken cancellationToken = default)
where TRequest : Request<TRequestParameters>
where TResponse : ElasticsearchResponse, new()
where TRequestParameters : RequestParameters, new() =>
_client.DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, parameters, cancellationToken: cancellationToken);
where TRequestParameters : RequestParameters, new()
{
if (_client is null)
ThrowHelper.ThrowInvalidOperationException(InvalidOperation);

return _client.DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, cancellationToken: cancellationToken);
}

internal Task<TResponse> DoRequestAsync<TRequest, TResponse, TRequestParameters>(
TRequest request,
TRequestParameters parameters,
CancellationToken cancellationToken = default)
where TRequest : Request<TRequestParameters>
where TResponse : ElasticsearchResponse, new()
where TRequestParameters : RequestParameters, new() =>
_client.DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, cancellationToken: cancellationToken);
where TRequestParameters : RequestParameters, new()
{
if (_client is null)
ThrowHelper.ThrowInvalidOperationException(InvalidOperation);

return _client.DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, parameters, cancellationToken: cancellationToken);
}

internal Task<TResponse> DoRequestAsync<TRequest, TResponse, TRequestParameters>(
TRequest request,
Expand All @@ -63,6 +86,11 @@ internal Task<TResponse> DoRequestAsync<TRequest, TResponse, TRequestParameters>
CancellationToken cancellationToken = default)
where TRequest : Request<TRequestParameters>
where TResponse : ElasticsearchResponse, new()
where TRequestParameters : RequestParameters, new() =>
_client.DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, parameters, forceConfiguration, cancellationToken);
where TRequestParameters : RequestParameters, new()
{
if (_client is null)
ThrowHelper.ThrowInvalidOperationException(InvalidOperation);

return _client.DoRequestAsync<TRequest, TResponse, TRequestParameters>(request, parameters, forceConfiguration, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,11 @@ internal static void ThrowUnknownTaggedUnionVariantJsonException(string variantT
throw new JsonException($"Encounted an unsupported variant tag '{variantTag}' on '{SimplifiedFullName(interfaceType)}', which could not be deserialised.");

[MethodImpl(MethodImplOptions.NoInlining)]
internal static void ThrowInvalidOperationException(string message) =>
throw new InvalidOperationException(message);

[MethodImpl(MethodImplOptions.NoInlining)]
#pragma warning disable IDE0057 // Use range operator
private static string SimplifiedFullName(Type type) => type.FullName.Substring(30);
#pragma warning restore IDE0057 // Use range operator
}
Loading