126 lines
4.1 KiB
C#
126 lines
4.1 KiB
C#
|
|
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;
|
||
|
|
}
|