using CloudServicesCrud;
using Grpc.Core;
using Xunit;
namespace DynamicCrud.Tests.Grpc;
///
/// Rutas de error del CRUD dinámico. El contrato del servicio es devolver el fallo dentro de la
/// respuesta (success=false) y no propagar excepciones gRPC: un RpcException con
/// status Internal significa que algo escapó del manejo de errores del servicio.
/// Todos los casos son de lectura.
///
[Collection(GrpcCollection.Name)]
[Trait("suite", "grpc")]
public class CrudErrorPathTests
{
private const string ModeloReal = "region";
private readonly CloudServicesCrud.DynamicCrudService.DynamicCrudServiceClient _client;
public CrudErrorPathTests(GrpcFixture fixture) =>
_client = new CloudServicesCrud.DynamicCrudService.DynamicCrudServiceClient(fixture.Channel);
[Fact]
public void GetAll_ModeloInexistente_DevuelveErrorControlado()
{
var resp = SinExcepcion(() => _client.GetAll(
new GetAllRequest { Model = "modelo_que_no_existe", Limit = 1 }));
Assert.False(resp.Success, "un modelo inexistente no debería reportar éxito");
Assert.Equal("error", resp.Status);
Assert.Contains("no encontrado", resp.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void GetFields_ModeloInexistente_DevuelveErrorControlado()
{
var resp = SinExcepcion(() => _client.GetFields(
new GetFieldsRequest { Model = "modelo_que_no_existe" }));
Assert.False(resp.Success);
Assert.Empty(resp.Fields);
}
[Fact]
public void Count_ModeloInexistente_DevuelveErrorControlado()
{
var resp = SinExcepcion(() => _client.Count(
new SearchRequest { Model = "modelo_que_no_existe" }));
Assert.False(resp.Success);
Assert.Equal(0, resp.Total);
}
[Fact]
public void GetById_IdInexistente_DevuelveErrorYNoUnRegistroVacio()
{
var resp = SinExcepcion(() => _client.GetById(
new GetByIdRequest { Model = ModeloReal, Id = "2147483647" }));
Assert.False(resp.Success, "un id inexistente no debería reportar éxito");
}
[Fact]
public void GetAll_ColumnaInexistente_DevuelveErrorDeValidacion()
{
var req = new GetAllRequest { Model = ModeloReal, Limit = 1 };
req.Columns.Add("columna_inventada");
var resp = SinExcepcion(() => _client.GetAll(req));
Assert.False(resp.Success);
Assert.Contains("no válidas", resp.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Search_CampoDeFiltroInexistente_DevuelveErrorDeValidacion()
{
var req = new SearchRequest { Model = ModeloReal };
req.And.Add(new FilterCondition { Field = "campo_inventado", Op = "eq", Value = "1" });
var resp = SinExcepcion(() => _client.Search(req));
Assert.False(resp.Success);
Assert.Contains("filtro no válidos", resp.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Search_OperadorInvalido_DevuelveErrorDeValidacion()
{
var req = new SearchRequest { Model = ModeloReal };
req.And.Add(new FilterCondition { Field = "idregion", Op = "operador_raro", Value = "1" });
var resp = SinExcepcion(() => _client.Search(req));
Assert.False(resp.Success);
Assert.Contains("operadores no válidos", resp.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Search_OrderByCampoInexistente_DevuelveErrorDeValidacion()
{
var req = new SearchRequest { Model = ModeloReal };
req.OrderBy.Add(new OrderBy { Field = "campo_inventado", Direction = "asc" });
var resp = SinExcepcion(() => _client.Search(req));
Assert.False(resp.Success);
Assert.Contains("order_by no válidos", resp.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Count_CampoDeFiltroInexistente_DevuelveErrorDeValidacion()
{
var req = new SearchRequest { Model = ModeloReal };
req.And.Add(new FilterCondition { Field = "campo_inventado", Op = "eq", Value = "1" });
var resp = SinExcepcion(() => _client.Count(req));
Assert.False(resp.Success);
}
[Fact]
public void Search_PaginaNegativa_NoRompeElServicio()
{
// No se afirma un resultado concreto: lo que se verifica es que una entrada absurda
// no tumbe la llamada con Internal ni deje la conexión colgada.
var resp = SinExcepcion(() => _client.Search(
new SearchRequest { Model = ModeloReal, Page = -1, PageSize = -10 }));
Assert.NotNull(resp);
}
///
/// Ejecuta la llamada y convierte cualquier en un fallo explícito:
/// el servicio debe reportar los errores en la respuesta, no como status gRPC.
///
private static T SinExcepcion(Func call)
{
try
{
return call();
}
catch (RpcException ex)
{
Assert.Fail(
$"el servicio lanzó RpcException ({ex.StatusCode}) en vez de devolver un error " +
$"en la respuesta: {ex.Status.Detail}");
throw; // inalcanzable; mantiene contento al compilador
}
}
}