diff --git a/src/Couchbase.Analytics/Async/QueryHandle.cs b/src/Couchbase.Analytics/Async/QueryHandle.cs
index 5e4f14b..46f1142 100644
--- a/src/Couchbase.Analytics/Async/QueryHandle.cs
+++ b/src/Couchbase.Analytics/Async/QueryHandle.cs
@@ -22,7 +22,7 @@
using System.Text.Json;
using Couchbase.AnalyticsClient.Internal;
using Couchbase.AnalyticsClient.Options;
-using Couchbase.AnalyticsClient.Results;
+using Couchbase.AnalyticsClient.Query;
namespace Couchbase.AnalyticsClient.Async;
@@ -34,6 +34,10 @@ public class QueryHandle
{
private readonly IAnalyticsService _analyticsService;
+ internal string? Status { get; }
+
+ internal AsyncQueryMetrics? Metrics { get; }
+
///
/// The query handle string used to poll for the result handle.
///
@@ -44,11 +48,22 @@ public class QueryHandle
///
public string RequestId { get; }
- internal QueryHandle(string handle, string requestId, IAnalyticsService analyticsService)
+ internal QueryHandle(string handle, string requestId, JsonElement root, IAnalyticsService analyticsService)
{
Handle = handle ?? throw new ArgumentNullException(nameof(handle));
RequestId = requestId ?? throw new ArgumentNullException(nameof(requestId));
_analyticsService = analyticsService ?? throw new ArgumentNullException(nameof(analyticsService));
+
+ if (root.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
+ {
+ throw new ArgumentException("The JSON response element must not be empty or null.", nameof(root));
+ }
+
+ Status = root.TryGetProperty("status", out var statusProp) ? statusProp.GetString() : null;
+ if (root.TryGetProperty("metrics", out var metricsElement))
+ {
+ Metrics = JsonSerializer.Deserialize(metricsElement.GetRawText());
+ }
}
///
@@ -93,4 +108,12 @@ public Task CancelAsync(Func options, Cancellation
cancelOptions = options.Invoke(cancelOptions);
return CancelAsync(cancelOptions, cancellationToken);
}
+
+ ///
+ public override string ToString()
+ {
+ var elapsed = Metrics?.ElapsedTime?.TotalMilliseconds;
+ var metricsStr = elapsed.HasValue ? $"{elapsed}ms elapsed" : "none";
+ return $"QueryHandle [RequestId={RequestId}, Handle={Handle}, Status={Status ?? "unknown"}, Metrics={{{metricsStr}}}]";
+ }
}
diff --git a/src/Couchbase.Analytics/Async/QueryResultHandle.cs b/src/Couchbase.Analytics/Async/QueryResultHandle.cs
index 308fea4..def5e30 100644
--- a/src/Couchbase.Analytics/Async/QueryResultHandle.cs
+++ b/src/Couchbase.Analytics/Async/QueryResultHandle.cs
@@ -19,8 +19,10 @@
* ************************************************************/
#endregion
+using System.Text.Json;
using Couchbase.AnalyticsClient.Internal;
using Couchbase.AnalyticsClient.Options;
+using Couchbase.AnalyticsClient.Query;
using Couchbase.AnalyticsClient.Results;
namespace Couchbase.AnalyticsClient.Async;
@@ -33,16 +35,38 @@ public class QueryResultHandle
private readonly string _handlePath;
private readonly IAnalyticsService _analyticsService;
+ internal string? Status { get; }
+
+ internal AsyncQueryMetrics? Metrics { get; }
+
+ internal int? ResultCount { get; }
+
///
/// The request ID assigned by the server when the query was submitted.
///
public string RequestId { get; }
- internal QueryResultHandle(string handlePath, string requestId, IAnalyticsService analyticsService)
+ internal QueryResultHandle(string handlePath, string requestId, JsonElement root, IAnalyticsService analyticsService)
{
_handlePath = handlePath ?? throw new ArgumentNullException(nameof(handlePath));
RequestId = requestId ?? throw new ArgumentNullException(nameof(requestId));
_analyticsService = analyticsService ?? throw new ArgumentNullException(nameof(analyticsService));
+
+ if (root.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null)
+ {
+ throw new ArgumentException("The JSON response element must not be empty or null.", nameof(root));
+ }
+
+ Status = root.TryGetProperty("status", out var statusProp) ? statusProp.GetString() : null;
+ if (root.TryGetProperty("metrics", out var metricsElement))
+ {
+ Metrics = JsonSerializer.Deserialize(metricsElement.GetRawText());
+ }
+
+ if (root.TryGetProperty("resultCount", out var resultCountProp) && resultCountProp.TryGetInt32(out var resultCount))
+ {
+ ResultCount = resultCount;
+ }
}
///
@@ -87,4 +111,14 @@ public Task DiscardResultsAsync(Func
+ public override string ToString()
+ {
+ var elapsed = Metrics?.ElapsedTime?.TotalMilliseconds;
+ var metricsStr = elapsed.HasValue ? $"{elapsed}ms elapsed" : "none";
+ var countStr = ResultCount.HasValue ? $", ResultCount={ResultCount}" : "";
+
+ return $"QueryResultHandle [RequestId={RequestId}, Status={Status ?? "unknown"}{countStr}, Metrics={{{metricsStr}}}]";
+ }
}
diff --git a/src/Couchbase.Analytics/Cluster.cs b/src/Couchbase.Analytics/Cluster.cs
index 6477974..e659b27 100644
--- a/src/Couchbase.Analytics/Cluster.cs
+++ b/src/Couchbase.Analytics/Cluster.cs
@@ -30,7 +30,7 @@
namespace Couchbase.AnalyticsClient;
-public class Cluster : IDisposable
+public partial class Cluster : IDisposable
{
private volatile ICredential _credential;
private readonly ClusterOptions _clusterOptions;
@@ -52,6 +52,8 @@ private Cluster(ICredential credential, ClusterOptions clusterOptions)
_logger = _serviceProvider.GetRequiredService>();
_analyticsService = new LazyService(_serviceProvider);
+
+ LogClusterCreated(_logger, clusterOptions.ConnectionString);
}
///
@@ -164,6 +166,7 @@ public void UpdateCredential(ICredential newCredential)
throw new InvalidOperationException(
$"Cannot change credential type from {current.GetType().Name} to {newCredential.GetType().Name}.");
_credential = newCredential;
+ LogCredentialUpdated(_logger, current.GetType().Name);
}
public Task ExecuteQueryAsync(string statement, Func options, CancellationToken cancellationToken = default)
@@ -222,5 +225,19 @@ public void Dispose()
{
disposableProvider.Dispose();
}
+ LogClusterDisposed(_logger);
}
+
+ #region Logging
+
+ [LoggerMessage(1, LogLevel.Information, "Analytics Cluster initialized for connection: {ConnectionString}")]
+ private static partial void LogClusterCreated(ILogger logger, string connectionString);
+
+ [LoggerMessage(2, LogLevel.Information, "Analytics Cluster credentials dynamically updated (Type: {CredentialType})")]
+ private static partial void LogCredentialUpdated(ILogger logger, string credentialType);
+
+ [LoggerMessage(3, LogLevel.Information, "Analytics Cluster disposed. Releasing managed resources.")]
+ private static partial void LogClusterDisposed(ILogger logger);
+
+ #endregion
}
diff --git a/src/Couchbase.Analytics/Exceptions/QueryNotFoundException.cs b/src/Couchbase.Analytics/Exceptions/QueryNotFoundException.cs
index 40f4720..d692ca0 100644
--- a/src/Couchbase.Analytics/Exceptions/QueryNotFoundException.cs
+++ b/src/Couchbase.Analytics/Exceptions/QueryNotFoundException.cs
@@ -33,5 +33,5 @@ public QueryNotFoundException(string message) : base(message) { }
public QueryNotFoundException(string message, Exception innerException) : base(message, innerException) { }
- internal QueryNotFoundException(string message, Exception innerException, Couchbase.AnalyticsClient.Internal.Retry.ErrorContext errorContext) : base(message, innerException, errorContext) { }
+ internal QueryNotFoundException(string message, Exception innerException, Internal.Retry.ErrorContext errorContext) : base(message, innerException, errorContext) { }
}
diff --git a/src/Couchbase.Analytics/HTTP/CertificateCredential.cs b/src/Couchbase.Analytics/HTTP/CertificateCredential.cs
index 84a7f9f..a74a617 100644
--- a/src/Couchbase.Analytics/HTTP/CertificateCredential.cs
+++ b/src/Couchbase.Analytics/HTTP/CertificateCredential.cs
@@ -90,13 +90,9 @@ public static CertificateCredential FromPkcs12(string path, string? password = n
public static CertificateCredential FromPem(string certPath, string keyPath) =>
new(X509Certificate2.CreateFromPemFile(certPath, keyPath));
- ///
- /// Excludes sensitive certificate details from the record's ToString output.
- /// Only Subject and Thumbprint are included.
- ///
private bool PrintMembers(StringBuilder builder)
{
- builder.Append($"{nameof(Certificate.Subject)} = {Certificate.Subject}, {nameof(Certificate.Thumbprint)} = {Certificate.Thumbprint}");
+ builder.Append($"{nameof(Certificate)} = [Subject = {Certificate.Subject}, Thumbprint = {Certificate.Thumbprint}]");
return true;
}
}
diff --git a/src/Couchbase.Analytics/HTTP/Credential.cs b/src/Couchbase.Analytics/HTTP/Credential.cs
index 44ba533..7f37142 100644
--- a/src/Couchbase.Analytics/HTTP/Credential.cs
+++ b/src/Couchbase.Analytics/HTTP/Credential.cs
@@ -50,7 +50,7 @@ public static Credential Create(string username, string password)
/// Excludes from the record's ToString output
/// to prevent leaking encoded credentials into logs.
///
- protected virtual bool PrintMembers(System.Text.StringBuilder builder)
+ protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append($"{nameof(Username)} = {Username}");
return true;
diff --git a/src/Couchbase.Analytics/HTTP/JwtCredential.cs b/src/Couchbase.Analytics/HTTP/JwtCredential.cs
index cb98470..3b45158 100644
--- a/src/Couchbase.Analytics/HTTP/JwtCredential.cs
+++ b/src/Couchbase.Analytics/HTTP/JwtCredential.cs
@@ -20,6 +20,7 @@
#endregion
using System.Net.Http.Headers;
+using System.Text;
namespace Couchbase.AnalyticsClient.HTTP;
@@ -43,11 +44,7 @@ public static JwtCredential Create(string token)
return new(token);
}
- ///
- /// Excludes the full token and from the record's
- /// ToString output to prevent leaking credentials into logs.
- ///
- private bool PrintMembers(System.Text.StringBuilder builder)
+ private bool PrintMembers(StringBuilder builder)
{
builder.Append($"{nameof(Token)} = <{Token.Length} chars>");
return true;
diff --git a/src/Couchbase.Analytics/Internal/AnalyticsService.cs b/src/Couchbase.Analytics/Internal/AnalyticsService.cs
index 48f8dbd..6fd1786 100644
--- a/src/Couchbase.Analytics/Internal/AnalyticsService.cs
+++ b/src/Couchbase.Analytics/Internal/AnalyticsService.cs
@@ -146,7 +146,7 @@ private async Task ExecuteWithRetryAsync(string statement, QueryOp
}
try
{
- LogQueryAttemptStarting(_logger, attempt + 1, options.ClientContextId, _redactor.UserData(statement), stopwatch.Elapsed.TotalMilliseconds);
+ LogQueryAttemptStarting(_logger, attempt + 1, options.ClientContextId, options.QueryContext?.ToString() ?? "", _redactor.UserData(statement), stopwatch.Elapsed.TotalMilliseconds);
var result = await ExecuteQueryAsync(content, httpClient, options.AsStreaming, deserializer, errorContext, cancellationToken).ConfigureAwait(false);
@@ -172,7 +172,7 @@ private async Task ExecuteWithRetryAsync(string statement, QueryOp
}
catch (HttpRequestException httpRequestException)
{
- LogQueryAttemptFailed(_logger, httpRequestException, attempt + 1, options.ClientContextId, _redactor.UserData(statement),
+ LogQueryAttemptFailed(_logger, httpRequestException, attempt + 1, options.ClientContextId, options.QueryContext?.ToString() ?? "", _redactor.UserData(statement),
httpRequestException.Message, stopwatch.Elapsed.TotalMilliseconds);
// "No successful connection(s)" is retryable
@@ -238,7 +238,7 @@ public async Task StartQueryAsync(string statement, StartQueryOptio
try
{
- LogAsyncStartQueryAttempt(_logger, attempt + 1, _redactor.SystemData(Uri), options.ClientContextId, _redactor.UserData(statement), stopwatch.Elapsed.TotalMilliseconds);
+ LogAsyncStartQueryAttempt(_logger, attempt + 1, _redactor.SystemData(Uri), options.ClientContextId, options.QueryContext?.ToString() ?? "", _redactor.UserData(statement), stopwatch.Elapsed.TotalMilliseconds);
var request = new HttpRequestMessage(HttpMethod.Post, Uri) { Content = content };
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken)
@@ -289,11 +289,11 @@ public async Task StartQueryAsync(string statement, StartQueryOptio
}
LogAsyncStartQuerySucceeded(_logger, options.ClientContextId, _redactor.SystemData(handlePath), _redactor.SystemData(requestId), (int)response.StatusCode);
- return new QueryHandle(handlePath, requestId, this);
+ return new QueryHandle(handlePath, requestId, root, this);
}
catch (HttpRequestException httpRequestException)
{
- LogAsyncStartQueryFailed(_logger, httpRequestException, attempt + 1, options.ClientContextId, _redactor.UserData(statement), httpRequestException.Message);
+ LogAsyncStartQueryFailed(_logger, httpRequestException, attempt + 1, options.ClientContextId, options.QueryContext?.ToString() ?? "", _redactor.UserData(statement), httpRequestException.Message);
if (httpRequestException.InnerException is AggregateException aggregateEx)
{
@@ -333,7 +333,7 @@ public async Task StartQueryAsync(string statement, StartQueryOptio
var statusUri = Uri.TryCreate(handle.Handle, UriKind.Absolute, out var absUri) && (absUri.Scheme == Uri.UriSchemeHttp || absUri.Scheme == Uri.UriSchemeHttps)
? absUri
: new Uri(_baseUri, handle.Handle);
-
+
var request = new HttpRequestMessage(HttpMethod.Get, statusUri);
LogFetchResultHandleRequest(_logger, _redactor.SystemData(statusUri), _redactor.SystemData(handle.Handle));
@@ -390,19 +390,19 @@ public async Task StartQueryAsync(string statement, StartQueryOptio
if (!string.Equals(status, "success", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(status, "stopped", StringComparison.OrdinalIgnoreCase) ||
+ if (string.Equals(status, "stopped", StringComparison.OrdinalIgnoreCase) ||
string.Equals(status, "aborted", StringComparison.OrdinalIgnoreCase) ||
string.Equals(status, "closed", StringComparison.OrdinalIgnoreCase))
{
throw new QueryNotFoundException($"Query has been discarded or canceled (status: {status}).");
}
-
+
if (string.Equals(status, "timeout", StringComparison.OrdinalIgnoreCase))
{
throw new AnalyticsTimeoutException("The query evaluation timed out on the server.");
}
-
- if (string.Equals(status, "fatal", StringComparison.OrdinalIgnoreCase) ||
+
+ if (string.Equals(status, "fatal", StringComparison.OrdinalIgnoreCase) ||
string.Equals(status, "failed", StringComparison.OrdinalIgnoreCase) ||
string.Equals(status, "errors", StringComparison.OrdinalIgnoreCase))
{
@@ -414,7 +414,7 @@ public async Task StartQueryAsync(string statement, StartQueryOptio
}
throw new AnalyticsException($"Query execution failed on the server (status: {status}).");
}
-
+
throw new AnalyticsException($"Query status fetch failed with unrecognized status: {status}");
}
@@ -424,7 +424,7 @@ public async Task StartQueryAsync(string statement, StartQueryOptio
throw new InvalidOperationException("Query status indicates success but no result handle was provided by the server.");
}
- return new QueryResultHandle(resultHandle, handle.RequestId, this);
+ return new QueryResultHandle(resultHandle, handle.RequestId, root, this);
}
catch (TaskCanceledException taskCanceledEx)
{
@@ -441,7 +441,7 @@ public async Task FetchResultsAsync(string requestId, string handl
var resultUri = Uri.TryCreate(handlePath, UriKind.Absolute, out var absUri) && (absUri.Scheme == Uri.UriSchemeHttp || absUri.Scheme == Uri.UriSchemeHttps)
? absUri
: new Uri(_baseUri, handlePath);
-
+
var request = new HttpRequestMessage(HttpMethod.Get, resultUri);
LogFetchResultsRequest(_logger, _redactor.SystemData(resultUri), _redactor.SystemData(handlePath));
@@ -489,7 +489,7 @@ public async Task DiscardResultsAsync(string requestId, string handlePath, Disca
var resultUri = Uri.TryCreate(handlePath, UriKind.Absolute, out var absUri) && (absUri.Scheme == Uri.UriSchemeHttp || absUri.Scheme == Uri.UriSchemeHttps)
? absUri
: new Uri(_baseUri, handlePath);
-
+
var request = new HttpRequestMessage(HttpMethod.Delete, resultUri);
LogDiscardResultsRequest(_logger, _redactor.SystemData(resultUri), _redactor.SystemData(handlePath));
@@ -579,8 +579,8 @@ private static Exception ThrowTooManyRetries(ErrorContext errorContext)
#region Logging
- [LoggerMessage(1, LogLevel.Debug, "Analytics query attempt {Attempt} starting for {ClientContextId}: {Statement} (elapsed: {Elapsed}ms)")]
- private static partial void LogQueryAttemptStarting(ILogger logger, int attempt, string? clientContextId, Redacted statement, double elapsed);
+ [LoggerMessage(1, LogLevel.Debug, "Analytics query attempt {Attempt} starting for {ClientContextId} on context [{QueryContext}]: {Statement} (elapsed: {Elapsed}ms)")]
+ private static partial void LogQueryAttemptStarting(ILogger logger, int attempt, string? clientContextId, string queryContext, Redacted statement, double elapsed);
[LoggerMessage(2, LogLevel.Debug, "Received retriable server errors for ClientContextId {ClientContextId}, retrying...")]
private static partial void LogRetriableServerErrors(ILogger logger, string? clientContextId);
@@ -588,14 +588,14 @@ private static Exception ThrowTooManyRetries(ErrorContext errorContext)
[LoggerMessage(3, LogLevel.Debug, "HttpRequestException is not retriable, failing immediately")]
private static partial void LogNonRetriableHttpException(ILogger logger);
- [LoggerMessage(4, LogLevel.Debug, "Analytics query attempt {Attempt} for ClientContextId {ClientContextId}: {Statement} failed: {Error} (elapsed: {Elapsed}ms)")]
- private static partial void LogQueryAttemptFailed(ILogger logger, Exception ex, int attempt, string? clientContextId, Redacted statement, string error, double elapsed);
+ [LoggerMessage(4, LogLevel.Debug, "Analytics query attempt {Attempt} for ClientContextId {ClientContextId} on context [{QueryContext}]: {Statement} failed: {Error} (elapsed: {Elapsed}ms)")]
+ private static partial void LogQueryAttemptFailed(ILogger logger, Exception ex, int attempt, string? clientContextId, string queryContext, Redacted statement, string error, double elapsed);
- [LoggerMessage(5, LogLevel.Debug, "Async StartQuery attempt {Attempt} sending POST to {Uri} for {ClientContextId}: {Statement} (elapsed: {Elapsed}ms)")]
- private static partial void LogAsyncStartQueryAttempt(ILogger logger, int attempt, Redacted uri, string? clientContextId, Redacted statement, double elapsed);
+ [LoggerMessage(5, LogLevel.Debug, "Async StartQuery attempt {Attempt} sending POST to {Uri} for {ClientContextId} on context [{QueryContext}]: {Statement} (elapsed: {Elapsed}ms)")]
+ private static partial void LogAsyncStartQueryAttempt(ILogger logger, int attempt, Redacted uri, string? clientContextId, string queryContext, Redacted statement, double elapsed);
- [LoggerMessage(6, LogLevel.Debug, "Async StartQuery attempt {Attempt} for {ClientContextId}: {Statement} failed: {Error}")]
- private static partial void LogAsyncStartQueryFailed(ILogger logger, Exception ex, int attempt, string? clientContextId, Redacted statement, string error);
+ [LoggerMessage(6, LogLevel.Debug, "Async StartQuery attempt {Attempt} for {ClientContextId} on context [{QueryContext}]: {Statement} failed: {Error}")]
+ private static partial void LogAsyncStartQueryFailed(ILogger logger, Exception ex, int attempt, string? clientContextId, string queryContext, Redacted statement, string error);
[LoggerMessage(7, LogLevel.Debug, "DiscardResults returned 404 for handle {Handle} — already discarded or canceled.")]
private static partial void LogDiscardResults404(ILogger logger, Redacted handle);
diff --git a/src/Couchbase.Analytics/Internal/DI/CouchbaseServiceProvider.cs b/src/Couchbase.Analytics/Internal/DI/CouchbaseServiceProvider.cs
index 8db04a5..5a12d8b 100644
--- a/src/Couchbase.Analytics/Internal/DI/CouchbaseServiceProvider.cs
+++ b/src/Couchbase.Analytics/Internal/DI/CouchbaseServiceProvider.cs
@@ -19,7 +19,6 @@
* ************************************************************/
#endregion
-using System;
using System.Collections.ObjectModel;
namespace Couchbase.AnalyticsClient.Internal.DI;
diff --git a/src/Couchbase.Analytics/Internal/DI/SingletonServiceFactory.cs b/src/Couchbase.Analytics/Internal/DI/SingletonServiceFactory.cs
index 3727612..4f018f4 100644
--- a/src/Couchbase.Analytics/Internal/DI/SingletonServiceFactory.cs
+++ b/src/Couchbase.Analytics/Internal/DI/SingletonServiceFactory.cs
@@ -19,7 +19,6 @@
* ************************************************************/
#endregion
-using System;
using System.Diagnostics.CodeAnalysis;
using Couchbase.AnalyticsClient.Utils;
diff --git a/src/Couchbase.Analytics/Internal/HTTP/CouchbaseHttpClientFactory.cs b/src/Couchbase.Analytics/Internal/HTTP/CouchbaseHttpClientFactory.cs
index 7b2fe34..b6f8814 100644
--- a/src/Couchbase.Analytics/Internal/HTTP/CouchbaseHttpClientFactory.cs
+++ b/src/Couchbase.Analytics/Internal/HTTP/CouchbaseHttpClientFactory.cs
@@ -31,7 +31,7 @@
namespace Couchbase.AnalyticsClient.Internal.HTTP;
-internal class CouchbaseHttpClientFactory : ICouchbaseHttpClientFactory
+internal partial class CouchbaseHttpClientFactory : ICouchbaseHttpClientFactory
{
///
/// Grace period before disposing a retired handler, allowing in-flight requests to complete.
@@ -208,19 +208,22 @@ private void RecreateHandler(ICredential newCredential)
_sharedHandler = CreateClientHandler();
_lastKnownCredential = newCredential;
+ LogHandlerRecreated(_logger, newCredential.GetType().Name, RetiredHandlerDisposeDelay.TotalSeconds);
+
// Schedule deferred disposal of the old handler.
// In-flight requests may still reference it, so we wait before disposing.
- _ = DisposeAfterDelayAsync(oldHandler);
+ _ = DisposeAfterDelayAsync(oldHandler, _logger);
}
}
///
/// Disposes a retired handler after a grace period, allowing in-flight requests to drain.
///
- private static async Task DisposeAfterDelayAsync(AuthenticationHandler handler)
+ private static async Task DisposeAfterDelayAsync(AuthenticationHandler handler, ILogger logger)
{
await Task.Delay(RetiredHandlerDisposeDelay).ConfigureAwait(false);
handler.Dispose();
+ LogOldHandlerDisposed(logger);
}
public void Dispose()
@@ -229,4 +232,14 @@ public void Dispose()
}
public HttpCompletionOption DefaultCompletionOption { get; set; } = HttpCompletionOption.ResponseHeadersRead;
+
+ #region Logging
+
+ [LoggerMessage(1, LogLevel.Information, "HTTP handler rebuilt due to {CredentialType} credential change. Old handler scheduled for disposal in {DelaySeconds}s.")]
+ private static partial void LogHandlerRecreated(ILogger logger, string credentialType, double delaySeconds);
+
+ [LoggerMessage(2, LogLevel.Information, "Retired HTTP handler successfully disposed after grace period.")]
+ private static partial void LogOldHandlerDisposed(ILogger logger);
+
+ #endregion
}
diff --git a/src/Couchbase.Analytics/Internal/IAnalyticsService.cs b/src/Couchbase.Analytics/Internal/IAnalyticsService.cs
index 6201737..a4eac50 100644
--- a/src/Couchbase.Analytics/Internal/IAnalyticsService.cs
+++ b/src/Couchbase.Analytics/Internal/IAnalyticsService.cs
@@ -22,7 +22,6 @@
using Couchbase.AnalyticsClient.Async;
using Couchbase.AnalyticsClient.Options;
using Couchbase.AnalyticsClient.Results;
-using Couchbase.Core.Json;
namespace Couchbase.AnalyticsClient.Internal;
diff --git a/src/Couchbase.Analytics/Options/StartQueryOptions.cs b/src/Couchbase.Analytics/Options/StartQueryOptions.cs
index 783fe99..271aa27 100644
--- a/src/Couchbase.Analytics/Options/StartQueryOptions.cs
+++ b/src/Couchbase.Analytics/Options/StartQueryOptions.cs
@@ -21,7 +21,6 @@
using System.Text.Json;
using Couchbase.AnalyticsClient.Query;
-using Couchbase.Core.Json;
using Couchbase.Core.Utils;
namespace Couchbase.AnalyticsClient.Options;
diff --git a/src/Couchbase.Analytics/Query/AsyncQueryMetrics.cs b/src/Couchbase.Analytics/Query/AsyncQueryMetrics.cs
new file mode 100644
index 0000000..f8ddf5c
--- /dev/null
+++ b/src/Couchbase.Analytics/Query/AsyncQueryMetrics.cs
@@ -0,0 +1,26 @@
+#region License
+/* ************************************************************
+ *
+ * @author Couchbase
+ * @copyright 2025 Couchbase, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ************************************************************/
+#endregion
+
+namespace Couchbase.AnalyticsClient.Query;
+
+internal sealed class AsyncQueryMetrics : QueryMetricsBase
+{
+}
diff --git a/src/Couchbase.Analytics/Query/QueryMetrics.cs b/src/Couchbase.Analytics/Query/QueryMetrics.cs
index ea19991..308f27e 100644
--- a/src/Couchbase.Analytics/Query/QueryMetrics.cs
+++ b/src/Couchbase.Analytics/Query/QueryMetrics.cs
@@ -20,27 +20,11 @@
#endregion
using System.Text.Json.Serialization;
-using Couchbase.Core.Json;
namespace Couchbase.AnalyticsClient.Query;
-public sealed class QueryMetrics
+public sealed class QueryMetrics : QueryMetricsBase
{
- [JsonConverter(typeof(MillisecondsStringJsonConverter))]
- [JsonPropertyName("elapsedTime")]
- public TimeSpan? ElapsedTime { get; init; }
-
- [JsonConverter(typeof(MillisecondsStringJsonConverter))]
- [JsonPropertyName("executionTime")]
- public TimeSpan? ExecutionTime { get; init; }
-
- [JsonConverter(typeof(MillisecondsStringJsonConverter))]
- [JsonPropertyName("compileTime")]
- public TimeSpan? CompileTime { get; init; }
-
- [JsonConverter(typeof(MillisecondsStringJsonConverter))]
- [JsonPropertyName("queueWaitTime")]
- public TimeSpan? QueueWaitTime { get; init; }
[JsonPropertyName("resultCount")]
public int ResultCount { get; init; }
diff --git a/src/Couchbase.Analytics/Query/QueryMetricsBase.cs b/src/Couchbase.Analytics/Query/QueryMetricsBase.cs
new file mode 100644
index 0000000..9d6691f
--- /dev/null
+++ b/src/Couchbase.Analytics/Query/QueryMetricsBase.cs
@@ -0,0 +1,44 @@
+#region License
+/* ************************************************************
+ *
+ * @author Couchbase
+ * @copyright 2025 Couchbase, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * ************************************************************/
+#endregion
+
+using System.Text.Json.Serialization;
+using Couchbase.Core.Json;
+
+namespace Couchbase.AnalyticsClient.Query;
+
+public abstract class QueryMetricsBase
+{
+ [JsonConverter(typeof(MillisecondsStringJsonConverter))]
+ [JsonPropertyName("elapsedTime")]
+ public TimeSpan? ElapsedTime { get; init; }
+
+ [JsonConverter(typeof(MillisecondsStringJsonConverter))]
+ [JsonPropertyName("executionTime")]
+ public TimeSpan? ExecutionTime { get; init; }
+
+ [JsonConverter(typeof(MillisecondsStringJsonConverter))]
+ [JsonPropertyName("compileTime")]
+ public TimeSpan? CompileTime { get; init; }
+
+ [JsonConverter(typeof(MillisecondsStringJsonConverter))]
+ [JsonPropertyName("queueWaitTime")]
+ public TimeSpan? QueueWaitTime { get; init; }
+}
diff --git a/src/Couchbase.Analytics/Scope.cs b/src/Couchbase.Analytics/Scope.cs
index edccdd7..f737887 100644
--- a/src/Couchbase.Analytics/Scope.cs
+++ b/src/Couchbase.Analytics/Scope.cs
@@ -19,10 +19,10 @@
* ************************************************************/
#endregion
+using Couchbase.AnalyticsClient.Async;
using Couchbase.AnalyticsClient.Options;
using Couchbase.AnalyticsClient.Query;
using Couchbase.AnalyticsClient.Results;
-using Couchbase.AnalyticsClient.Async;
namespace Couchbase.AnalyticsClient;
diff --git a/tests/Couchbase.Analytics.FunctionalTests/AsyncAnalyticsTests.cs b/tests/Couchbase.Analytics.FunctionalTests/AsyncAnalyticsTests.cs
index e4720ee..6c86ce0 100644
--- a/tests/Couchbase.Analytics.FunctionalTests/AsyncAnalyticsTests.cs
+++ b/tests/Couchbase.Analytics.FunctionalTests/AsyncAnalyticsTests.cs
@@ -1,8 +1,7 @@
-using System.Text.Json;
+using Couchbase.AnalyticsClient.Async;
using Couchbase.AnalyticsClient.Exceptions;
using Couchbase.AnalyticsClient.FunctionalTests.Fixtures;
using Couchbase.AnalyticsClient.Options;
-using Couchbase.AnalyticsClient.Async;
using Xunit;
using Xunit.Abstractions;
@@ -34,13 +33,13 @@ public async Task Test_AsyncAnalytics_EndToEnd_Cluster()
Assert.NotNull(handle);
Assert.NotNull(handle.Handle);
Assert.NotNull(handle.RequestId);
-
+
_outputHelper.WriteLine($"Handle: {handle.Handle}");
_outputHelper.WriteLine($"RequestId: {handle.RequestId}");
// 2. Poll for the result handle
QueryResultHandle? resultHandle = null;
- for (int i = 0; i < 20; i++)
+ for (var i = 0; i < 20; i++)
{
resultHandle = await handle.FetchResultHandleAsync(new FetchResultHandleOptions());
if (resultHandle != null)
@@ -86,7 +85,7 @@ public async Task Test_AsyncAnalytics_Cancellation_Cluster()
// It's possible the cancel takes a brief moment to process gracefully on the server.
var ex = await Record.ExceptionAsync(async () =>
{
- for (int i = 0; i < 20; i++)
+ for (var i = 0; i < 20; i++)
{
var resultHandle = await handle.FetchResultHandleAsync(new FetchResultHandleOptions());
if (resultHandle != null)
@@ -101,7 +100,7 @@ public async Task Test_AsyncAnalytics_Cancellation_Cluster()
// The query should have been killed, resulting in a QueryNotFoundException when it's purged,
// or a cleanly mapped QueryException ("Job Killed") if the server responds gracefully before purging.
Assert.NotNull(ex);
- Assert.True(ex is QueryNotFoundException || ex is QueryException,
+ Assert.True(ex is QueryNotFoundException or QueryException,
$"Expected QueryNotFoundException or QueryException upon cancellation, but received: {ex.GetType().FullName}");
}
@@ -113,7 +112,7 @@ public async Task Test_AsyncAnalytics_DiscardResults_Cluster()
// Poll for the result handle
QueryResultHandle? resultHandle = null;
- for (int i = 0; i < 20; i++)
+ for (var i = 0; i < 20; i++)
{
resultHandle = await handle.FetchResultHandleAsync(new FetchResultHandleOptions());
if (resultHandle != null)
@@ -132,7 +131,7 @@ public async Task Test_AsyncAnalytics_DiscardResults_Cluster()
await resultHandle!.DiscardResultsAsync(new DiscardResultsOptions());
// Attempting to fetch the results after discarding should throw QueryNotFoundException
- await Assert.ThrowsAsync(async () =>
+ await Assert.ThrowsAsync(async () =>
{
await resultHandle.FetchResultsAsync(new FetchResultsOptions());
});
diff --git a/tests/Couchbase.Analytics.FunctionalTests/CertificateAuthenticationTests.cs b/tests/Couchbase.Analytics.FunctionalTests/CertificateAuthenticationTests.cs
index ec7ac9a..b895b87 100644
--- a/tests/Couchbase.Analytics.FunctionalTests/CertificateAuthenticationTests.cs
+++ b/tests/Couchbase.Analytics.FunctionalTests/CertificateAuthenticationTests.cs
@@ -1,4 +1,3 @@
-using Couchbase.AnalyticsClient.Exceptions;
using Couchbase.AnalyticsClient.FunctionalTests.Fixtures;
using Couchbase.AnalyticsClient.HTTP;
using Couchbase.AnalyticsClient.Options;
diff --git a/tests/Couchbase.Analytics.UnitTests/Async/QueryHandleTests.cs b/tests/Couchbase.Analytics.UnitTests/Async/QueryHandleTests.cs
index 5475d66..4ab9547 100644
--- a/tests/Couchbase.Analytics.UnitTests/Async/QueryHandleTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Async/QueryHandleTests.cs
@@ -1,7 +1,8 @@
+using System.Text.Json;
using Couchbase.AnalyticsClient.Async;
using Couchbase.AnalyticsClient.Internal;
using Couchbase.AnalyticsClient.Options;
-using Couchbase.AnalyticsClient.Results;
+using Couchbase.AnalyticsClient.UnitTests.Helpers;
using Moq;
using Xunit;
@@ -13,7 +14,7 @@ public class QueryHandleTests
public void Constructor_InitializesProperties()
{
var serviceMock = new Mock();
- var handle = new QueryHandle("test-handle", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryHandle("test-handle", "test-req", "{}", serviceMock.Object);
Assert.Equal("test-handle", handle.Handle);
Assert.Equal("test-req", handle.RequestId);
@@ -23,15 +24,15 @@ public void Constructor_InitializesProperties()
public async Task FetchResultHandleAsync_DelegatesToService()
{
var serviceMock = new Mock();
- var handle = new QueryHandle("test-handle", "test-req", serviceMock.Object);
- var expectedResult = new Mock("path", "req", serviceMock.Object).Object;
+ var handle = TestHandleFactory.CreateQueryHandle("test-handle", "test-req", "{}", serviceMock.Object);
+ var expectedResult = TestHandleFactory.CreateQueryResultHandle("path", "req", "{}", serviceMock.Object);
serviceMock.Setup(x => x.FetchResultHandleAsync(handle, It.IsAny(), It.IsAny()))
.ReturnsAsync(expectedResult);
var result = await handle.FetchResultHandleAsync(new FetchResultHandleOptions());
Assert.Same(expectedResult, result);
-
+
serviceMock.Verify(x => x.FetchResultHandleAsync(handle, It.IsAny(), default), Times.Once);
}
@@ -39,14 +40,14 @@ public async Task FetchResultHandleAsync_DelegatesToService()
public async Task CancelAsync_DelegatesToService()
{
var serviceMock = new Mock();
- var handle = new QueryHandle("test-handle", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryHandle("test-handle", "test-req", "{}", serviceMock.Object);
var options = new CancelOptions();
serviceMock.Setup(x => x.CancelQueryAsync("test-req", options, It.IsAny()))
.Returns(Task.CompletedTask);
await handle.CancelAsync(options);
-
+
serviceMock.Verify(x => x.CancelQueryAsync("test-req", options, default), Times.Once);
}
@@ -55,17 +56,18 @@ public void Constructor_NullArguments_ThrowsArgumentNullException()
{
var serviceMock = new Mock();
- Assert.Throws(() => new QueryHandle(null!, "req", serviceMock.Object));
- Assert.Throws(() => new QueryHandle("handle", null!, serviceMock.Object));
- Assert.Throws(() => new QueryHandle("handle", "req", null!));
+ Assert.Throws(() => TestHandleFactory.CreateQueryHandle(null!, "req", "{}", serviceMock.Object));
+ Assert.Throws(() => TestHandleFactory.CreateQueryHandle("handle", null!, "{}", serviceMock.Object));
+ Assert.Throws(() => new QueryHandle("handle", "req", default(JsonElement), serviceMock.Object));
+ Assert.Throws(() => TestHandleFactory.CreateQueryHandle("handle", "req", "{}", null!));
}
[Fact]
public async Task FetchResultHandleAsync_FluentOptions_DelegatesProperly()
{
var serviceMock = new Mock();
- var handle = new QueryHandle("test-handle", "test-req", serviceMock.Object);
- var expectedResult = new Mock("path", "req", serviceMock.Object).Object;
+ var handle = TestHandleFactory.CreateQueryHandle("test-handle", "test-req", "{}", serviceMock.Object);
+ var expectedResult = TestHandleFactory.CreateQueryResultHandle("path", "req", "{}", serviceMock.Object);
serviceMock.Setup(x => x.FetchResultHandleAsync(handle, It.IsAny(), It.IsAny()))
.ReturnsAsync(expectedResult);
@@ -81,7 +83,7 @@ public async Task FetchResultHandleAsync_FluentOptions_DelegatesProperly()
public async Task CancelAsync_FluentOptions_DelegatesProperly()
{
var serviceMock = new Mock();
- var handle = new QueryHandle("test-handle", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryHandle("test-handle", "test-req", "{}", serviceMock.Object);
serviceMock.Setup(x => x.CancelQueryAsync("test-req", It.IsAny(), It.IsAny()))
.Returns(Task.CompletedTask);
diff --git a/tests/Couchbase.Analytics.UnitTests/Async/QueryResultHandleTests.cs b/tests/Couchbase.Analytics.UnitTests/Async/QueryResultHandleTests.cs
index c913e8f..2f95a11 100644
--- a/tests/Couchbase.Analytics.UnitTests/Async/QueryResultHandleTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Async/QueryResultHandleTests.cs
@@ -1,7 +1,9 @@
+using System.Text.Json;
using Couchbase.AnalyticsClient.Async;
using Couchbase.AnalyticsClient.Internal;
using Couchbase.AnalyticsClient.Options;
using Couchbase.AnalyticsClient.Results;
+using Couchbase.AnalyticsClient.UnitTests.Helpers;
using Moq;
using Xunit;
@@ -13,7 +15,7 @@ public class QueryResultHandleTests
public void Constructor_InitializesProperties()
{
var serviceMock = new Mock();
- var handle = new QueryResultHandle("test-path", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryResultHandle("test-path", "test-req", "{}", serviceMock.Object);
Assert.Equal("test-req", handle.RequestId);
}
@@ -22,7 +24,7 @@ public void Constructor_InitializesProperties()
public async Task FetchResultsAsync_DelegatesToService()
{
var serviceMock = new Mock();
- var handle = new QueryResultHandle("test-path", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryResultHandle("test-path", "test-req", "{}", serviceMock.Object);
var expectedResult = new Mock().Object;
var options = new FetchResultsOptions();
@@ -31,7 +33,7 @@ public async Task FetchResultsAsync_DelegatesToService()
var result = await handle.FetchResultsAsync(options);
Assert.Same(expectedResult, result);
-
+
serviceMock.Verify(x => x.FetchResultsAsync("test-req", "test-path", options, default), Times.Once);
}
@@ -39,14 +41,14 @@ public async Task FetchResultsAsync_DelegatesToService()
public async Task DiscardResultsAsync_DelegatesToService()
{
var serviceMock = new Mock();
- var handle = new QueryResultHandle("test-path", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryResultHandle("test-path", "test-req", "{}", serviceMock.Object);
var options = new DiscardResultsOptions();
serviceMock.Setup(x => x.DiscardResultsAsync("test-req", "test-path", options, It.IsAny()))
.Returns(Task.CompletedTask);
await handle.DiscardResultsAsync(options);
-
+
serviceMock.Verify(x => x.DiscardResultsAsync("test-req", "test-path", options, default), Times.Once);
}
@@ -55,16 +57,17 @@ public void Constructor_NullArguments_ThrowsArgumentNullException()
{
var serviceMock = new Mock();
- Assert.Throws(() => new QueryResultHandle(null!, "req", serviceMock.Object));
- Assert.Throws(() => new QueryResultHandle("path", null!, serviceMock.Object));
- Assert.Throws(() => new QueryResultHandle("path", "req", null!));
+ Assert.Throws(() => TestHandleFactory.CreateQueryResultHandle(null!, "req", "{}", serviceMock.Object));
+ Assert.Throws(() => TestHandleFactory.CreateQueryResultHandle("path", null!, "{}", serviceMock.Object));
+ Assert.Throws(() => new QueryResultHandle("path", "req", default(JsonElement), serviceMock.Object));
+ Assert.Throws(() => TestHandleFactory.CreateQueryResultHandle("path", "req", "{}", null!));
}
[Fact]
public async Task FetchResultsAsync_FluentOptions_DelegatesProperly()
{
var serviceMock = new Mock();
- var handle = new QueryResultHandle("test-path", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryResultHandle("test-path", "test-req", "{}", serviceMock.Object);
var expectedResult = new Mock().Object;
serviceMock.Setup(x => x.FetchResultsAsync("test-req", "test-path", It.IsAny(), It.IsAny()))
@@ -81,7 +84,7 @@ public async Task FetchResultsAsync_FluentOptions_DelegatesProperly()
public async Task DiscardResultsAsync_FluentOptions_DelegatesProperly()
{
var serviceMock = new Mock();
- var handle = new QueryResultHandle("test-path", "test-req", serviceMock.Object);
+ var handle = TestHandleFactory.CreateQueryResultHandle("test-path", "test-req", "{}", serviceMock.Object);
serviceMock.Setup(x => x.DiscardResultsAsync("test-req", "test-path", It.IsAny(), It.IsAny()))
.Returns(Task.CompletedTask);
diff --git a/tests/Couchbase.Analytics.UnitTests/ClusterDisposalTests.cs b/tests/Couchbase.Analytics.UnitTests/ClusterDisposalTests.cs
index ce54bd5..fd90c11 100644
--- a/tests/Couchbase.Analytics.UnitTests/ClusterDisposalTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/ClusterDisposalTests.cs
@@ -1,4 +1,3 @@
-using System;
using Couchbase.AnalyticsClient;
using Couchbase.AnalyticsClient.HTTP;
using Microsoft.Extensions.Logging;
@@ -13,6 +12,7 @@ public class ClusterDisposalTests
public void Cluster_Dispose_DisposesAllRegisteredSingletons()
{
var mockLoggerFactory = new Mock();
+ mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance);
var cluster = Cluster.Create(
"http://localhost:8095",
diff --git a/tests/Couchbase.Analytics.UnitTests/Helpers/TestHandleFactory.cs b/tests/Couchbase.Analytics.UnitTests/Helpers/TestHandleFactory.cs
new file mode 100644
index 0000000..8bc4288
--- /dev/null
+++ b/tests/Couchbase.Analytics.UnitTests/Helpers/TestHandleFactory.cs
@@ -0,0 +1,18 @@
+using System.Text.Json;
+using Couchbase.AnalyticsClient.Async;
+using Couchbase.AnalyticsClient.Internal;
+
+namespace Couchbase.AnalyticsClient.UnitTests.Helpers;
+
+///
+/// Convenience factory for creating handle objects from raw JSON strings in tests.
+/// Keeps production constructors accepting only .
+///
+internal static class TestHandleFactory
+{
+ public static QueryHandle CreateQueryHandle(string handle, string requestId, string responseJson, IAnalyticsService service)
+ => new(handle, requestId, JsonDocument.Parse(responseJson).RootElement, service);
+
+ public static QueryResultHandle CreateQueryResultHandle(string handlePath, string requestId, string responseJson, IAnalyticsService service)
+ => new(handlePath, requestId, JsonDocument.Parse(responseJson).RootElement, service);
+}
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/AnalyticsServiceTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/AnalyticsServiceTests.cs
index 82534ef..8038c11 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/AnalyticsServiceTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/AnalyticsServiceTests.cs
@@ -1,13 +1,14 @@
using System.Net;
using System.Text;
+using Couchbase.AnalyticsClient.Async;
+using Couchbase.AnalyticsClient.Exceptions;
using Couchbase.AnalyticsClient.Internal;
using Couchbase.AnalyticsClient.Internal.HTTP;
using Couchbase.AnalyticsClient.Internal.Results;
using Couchbase.AnalyticsClient.Logging;
using Couchbase.AnalyticsClient.Options;
+using Couchbase.AnalyticsClient.UnitTests.Helpers;
using Couchbase.Core.Json;
-using Couchbase.AnalyticsClient.Async;
-using Couchbase.AnalyticsClient.Exceptions;
using Microsoft.Extensions.Logging;
using Moq;
using Moq.Protected;
@@ -181,7 +182,7 @@ public async Task FetchResultHandleAsync_When404_ThrowsQueryNotFoundException()
_loggerMock.Object,
new TypedRedactor(RedactionLevel.None));
- var handle = new QueryHandle("mock-handle", "mock-req", service);
+ var handle = TestHandleFactory.CreateQueryHandle("mock-handle", "mock-req", "{}", service);
// Act & Assert
await Assert.ThrowsAsync(() =>
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/DI/CouchbaseServiceProviderTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/DI/CouchbaseServiceProviderTests.cs
index 1eec904..2819401 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/DI/CouchbaseServiceProviderTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/DI/CouchbaseServiceProviderTests.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Collections.Generic;
using Couchbase.AnalyticsClient.Internal.DI;
using Moq;
using Xunit;
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonGenericServiceFactoryTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonGenericServiceFactoryTests.cs
index 605350f..ec6b150 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonGenericServiceFactoryTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonGenericServiceFactoryTests.cs
@@ -1,4 +1,3 @@
-using System;
using Couchbase.AnalyticsClient.Internal.DI;
using Moq;
using Xunit;
@@ -7,12 +6,12 @@ namespace Couchbase.Analytics.UnitTests.Internal.DI;
public interface IFakeGenericService : IDisposable { }
-public class FakeGenericService : IFakeGenericService
+public class FakeGenericService : IFakeGenericService
{
public static Action? OnDispose { get; set; }
-
+
public FakeGenericService() { }
-
+
public void Dispose()
{
OnDispose?.Invoke();
@@ -26,27 +25,27 @@ public void SingletonGenericServiceFactory_Dispose_DisposesAllCachedPermutations
{
// Arrange
var factory = new SingletonGenericServiceFactory(typeof(FakeGenericService<>));
-
+
var mockServiceProvider = new Mock();
factory.Initialize(mockServiceProvider.Object);
-
+
// Populate the concurrent dictionary with different permutations of the generic
factory.CreateService(typeof(IFakeGenericService));
factory.CreateService(typeof(IFakeGenericService));
factory.CreateService(typeof(IFakeGenericService));
-
- int disposeCount = 0;
+
+ var disposeCount = 0;
FakeGenericService.OnDispose = () => disposeCount++;
FakeGenericService.OnDispose = () => disposeCount++;
FakeGenericService.OnDispose = () => disposeCount++;
// Act
factory.Dispose();
-
+
// Assert
// The factory should naturally iterate and call Dispose exactly 3 times (once per instantiated T)
Assert.Equal(3, disposeCount);
-
+
// Cleanup statics
FakeGenericService.OnDispose = null;
FakeGenericService.OnDispose = null;
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonServiceFactoryTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonServiceFactoryTests.cs
index 91a35ce..8688a13 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonServiceFactoryTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/DI/SingletonServiceFactoryTests.cs
@@ -1,4 +1,3 @@
-using System;
using Couchbase.AnalyticsClient.Internal.DI;
using Moq;
using Xunit;
@@ -13,10 +12,10 @@ public void SingletonServiceFactory_Dispose_DisposesInnerSingleton()
// Arrange
var mockDisposable = new Mock();
var factory = new SingletonServiceFactory(mockDisposable.Object);
-
+
// Act
factory.Dispose();
-
+
// Assert
mockDisposable.Verify(d => d.Dispose(), Times.Once);
}
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/DI/TransientServiceFactoryTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/DI/TransientServiceFactoryTests.cs
index 6038e06..24c75a6 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/DI/TransientServiceFactoryTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/DI/TransientServiceFactoryTests.cs
@@ -1,4 +1,3 @@
-using System;
using Couchbase.AnalyticsClient.Internal.DI;
using Moq;
using Xunit;
@@ -13,16 +12,16 @@ public void TransientServiceFactory_Dispose_DoesNotDisposeGeneratedInstances()
// Arrange
var mockDisposable = new Mock();
var factory = new TransientServiceFactory(_ => mockDisposable.Object);
-
+
var mockServiceProvider = new Mock();
factory.Initialize(mockServiceProvider.Object);
-
+
// Act
// We simulate the lifetime: The user asks for a Transient object, then later the Cluster shuts down entirely.
var instance = factory.CreateService(typeof(IDisposable));
-
+
factory.Dispose();
-
+
// Assert
// We explicitly confirm that the Transient factory completely ignores the instances
// it generates, thus avoiding long-term memory leaks in the Cluster's DI container!
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/ExecuteQueryTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/ExecuteQueryTests.cs
index 861002a..f0284d5 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/ExecuteQueryTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/ExecuteQueryTests.cs
@@ -5,7 +5,7 @@
using Couchbase.AnalyticsClient.Options;
using Couchbase.AnalyticsClient.Query;
using Couchbase.AnalyticsClient.Results;
-using Couchbase.Core.Json;
+using Couchbase.AnalyticsClient.UnitTests.Helpers;
using Xunit;
using Xunit.Abstractions;
@@ -58,7 +58,7 @@ public Task SendAsync(string statement, QueryOptions options, Canc
public Task StartQueryAsync(string statement, StartQueryOptions options, CancellationToken cancellationToken = default)
{
LastStartOptions = options;
- return Task.FromResult(new QueryHandle("handle", "reqId", this));
+ return Task.FromResult(TestHandleFactory.CreateQueryHandle("handle", "reqId", "{}", this));
}
public Task FetchResultHandleAsync(QueryHandle handle, FetchResultHandleOptions options, CancellationToken cancellationToken = default)
diff --git a/tests/Couchbase.Analytics.UnitTests/Internal/HTTP/CouchbaseHttpClientFactoryTests.cs b/tests/Couchbase.Analytics.UnitTests/Internal/HTTP/CouchbaseHttpClientFactoryTests.cs
index 1272a5c..717a4db 100644
--- a/tests/Couchbase.Analytics.UnitTests/Internal/HTTP/CouchbaseHttpClientFactoryTests.cs
+++ b/tests/Couchbase.Analytics.UnitTests/Internal/HTTP/CouchbaseHttpClientFactoryTests.cs
@@ -1,7 +1,3 @@
-using System;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Couchbase.AnalyticsClient;
using Couchbase.AnalyticsClient.HTTP;
using Couchbase.AnalyticsClient.Internal.HTTP;
using Couchbase.AnalyticsClient.Options;
@@ -18,18 +14,18 @@ public async Task CouchbaseHttpClientFactory_Dispose_CascadesToUnderlyingHandler
// Arrange
var options = new ClusterOptions { ConnectionString = "http://localhost:8095" };
var factory = new CouchbaseHttpClientFactory(
- () => new Credential("Administrator", "password"),
- options,
+ () => new Credential("Administrator", "password"),
+ options,
new NullLogger()
);
-
+
// Create an active HTTP client wrapping the shared AuthenticationHandler -> SocketsHttpHandler
var httpClient = factory.Create();
// Act
// Executing Dispose must trigger teardowns down the chain.
factory.Dispose();
-
+
// Assert
// A perfectly disposed downstream handler natively refuses any new HttpClient execution
// by immediately throwing an ObjectDisposedException preflight.