books/backend/Books.Api.Tests/Helpers/GraphQLTestClient.cs

126 lines
4.1 KiB
C#
Raw Normal View History

using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Books.Api.Tests.Helpers;
/// <summary>
/// Helper client for executing GraphQL queries and mutations in tests.
/// </summary>
public class GraphQLTestClient(HttpClient httpClient)
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
/// <summary>
/// Executes a GraphQL query and returns the typed result.
/// </summary>
public async Task<GraphQLResponse<T>> QueryAsync<T>(
string query,
object? variables = null,
CancellationToken cancellationToken = default)
{
return await ExecuteAsync<T>(query, variables, cancellationToken);
}
/// <summary>
/// Executes a GraphQL mutation and returns the typed result.
/// </summary>
public async Task<GraphQLResponse<T>> MutateAsync<T>(
string mutation,
object? variables = null,
CancellationToken cancellationToken = default)
{
return await ExecuteAsync<T>(mutation, variables, cancellationToken);
}
/// <summary>
/// Executes a GraphQL operation and returns the raw JSON response.
/// </summary>
public async Task<string> ExecuteRawAsync(
string query,
object? variables = null,
CancellationToken cancellationToken = default)
{
var request = new GraphQLRequest(query, variables);
var json = JsonSerializer.Serialize(request, JsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("/graphql", content, cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
}
private async Task<GraphQLResponse<T>> ExecuteAsync<T>(
string query,
object? variables,
CancellationToken cancellationToken)
{
var request = new GraphQLRequest(query, variables);
var json = JsonSerializer.Serialize(request, JsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync("/graphql", content, cancellationToken);
var responseJson = await response.Content.ReadAsStringAsync(cancellationToken);
var result = JsonSerializer.Deserialize<GraphQLResponse<T>>(responseJson, JsonOptions);
return result ?? throw new InvalidOperationException("Failed to deserialize GraphQL response");
}
}
public record GraphQLRequest(
[property: JsonPropertyName("query")] string Query,
[property: JsonPropertyName("variables")] object? Variables = null);
public class GraphQLResponse<T>
{
[JsonPropertyName("data")]
public T? Data { get; set; }
[JsonPropertyName("errors")]
public List<GraphQLError>? Errors { get; set; }
public bool HasErrors => Errors?.Count > 0;
public void EnsureNoErrors()
{
if (HasErrors)
{
var errorDetails = Errors!.Select(e =>
{
var msg = e.Message;
if (e.Extensions != null)
{
foreach (var ext in e.Extensions)
{
msg += $"{Environment.NewLine} [{ext.Key}]: {ext.Value}";
}
}
return msg;
});
var messages = string.Join(Environment.NewLine, errorDetails);
throw new GraphQLException($"GraphQL errors occurred:{Environment.NewLine}{messages}", Errors!);
}
}
}
public class GraphQLError
{
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
[JsonPropertyName("path")]
public List<object>? Path { get; set; }
[JsonPropertyName("extensions")]
public Dictionary<string, object>? Extensions { get; set; }
}
public class GraphQLException(string message, List<GraphQLError> errors) : Exception(message)
{
public List<GraphQLError> Errors { get; } = errors;
}