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 } } }