using Administrationservices; using CloudServiceDAO; using EntityFrameworkDAOServices; using Grpc.Core; using Xunit; namespace DynamicCrud.Tests.Grpc; /// /// Lectura de los servicios legacy (servicioDAOService, administrationService), /// más ProfileService y CatalogService. Solo se invocan métodos de consulta. /// /// /// /// Los parámetros salen de , que los descubre con el CRUD dinámico: /// varios de los stored procedures reciben el filtro nulo y lo tratan como "no coincide con /// nada" en vez de "sin filtro", así que llamarlos en vacío no ejerce su lógica real. /// Cuando la tabla origen está vacía el caso termina temprano en lugar de fallar. /// /// /// Hay dos niveles de exigencia, según lo que se pueda afirmar en cada caso: /// acepta NotFound como respuesta válida, y /// lo trata como fallo porque el id vino de una fila que /// existe. La distinción es necesaria: la capa legacy atrapa las excepciones de MySQL y las /// devuelve como NotFound, así que sin ella un stored procedure inexistente se ve /// desde fuera exactamente igual que una tabla vacía. /// /// [Collection(GrpcCollection.Name)] [Trait("suite", "grpc")] public class LegacyReadTests { private readonly servicioDAOService.servicioDAOServiceClient _dao; private readonly administrationService.administrationServiceClient _admin; private readonly ProfileService.ProfileServiceClient _profiles; private readonly CatalogService.CatalogServiceClient _catalog; private readonly DataProbe _probe; public LegacyReadTests(GrpcFixture fixture) { _dao = new servicioDAOService.servicioDAOServiceClient(fixture.Channel); _admin = new administrationService.administrationServiceClient(fixture.Channel); _profiles = new ProfileService.ProfileServiceClient(fixture.Channel); _catalog = new CatalogService.CatalogServiceClient(fixture.Channel); _probe = new DataProbe( new CloudServicesCrud.DynamicCrudService.DynamicCrudServiceClient(fixture.Channel)); } // --------------------------------------------------------------- // servicioDAOService // --------------------------------------------------------------- [Fact] public void GetProductType_Responde() => Responde("getProductType", () => _dao.getProductType(new GetProductTypeRequest())); [Fact] public void GetPlansService_Responde() { // spslGetPlansServicesById filtra por service_id sin contemplar null. var idService = _probe.Int("plans", "serviceId"); if (idService is null) return; Responde("getPlansService", () => _dao.getPlansService( new GetPlansServiceRequest { IdService = idService.Value })); } [Fact] public void GetServicesCatalog_Responde() { var idProductType = _probe.Int("product_type", "id"); if (idProductType is null) return; Responde("getServicesCatalog", () => _dao.getServicesCatalog( new CloudServiceDAO.GetServicesByProductTypeRequest { IdProductType = idProductType.Value })); } [Fact] public void GetServicesAllContracted_Responde() { var id = _probe.Int("contracted_services", "contractedServicesId"); if (id is null) return; Responde("getServicesAllContracted", () => _dao.getServicesAllContracted( new GetAllContractedServicesRequest { ContractedServicesId = id.Value })); } [Fact] public void GetIpServiceById_Responde() { var idFirewall = _probe.Int("firewall", "firewallId"); if (idFirewall is null) return; RespondeConDatos("getIpServiceById", () => _dao.getIpServiceById( new GetIpServiceByIdRequest { IdFirewall = idFirewall.Value })); } [Fact] public void GetUserVCD_Responde() { var userId = _probe.Int("user_vcd", "userVcdId"); if (userId is null) return; RespondeConDatos("getUserVCD", () => _dao.getUserVCD( new CloudServiceDAO.GetUserVCDRequest { UserId = userId.Value })); } [Fact] public void GetOrgVCD_Responde() { var orgId = _probe.Int("org_vcd", "orgVcdId"); if (orgId is null) return; RespondeConDatos("getOrgVCD", () => _dao.getOrgVCD( new CloudServiceDAO.GetOrgVCDRequest { OrgId = orgId.Value })); } [Fact] public void GetRequestQueueService_Responde() { var id = _probe.Int("requests_queue", "requestQueueId"); if (id is null) return; Responde("getRequestQueueService", () => _dao.getRequestQueueService( new GetRequestQueueRequest { RequestQueueId = id.Value })); } [Fact] public void GetLicenses_Responde() { var id = _probe.Int("licenses", "idlicenses"); if (id is null) return; Responde("GetLicenses", () => _dao.GetLicenses(new GetLicensesRequest { Idlicenses = id.Value })); } [Fact] public void GetStatusCatalog_Responde() { var id = _probe.Int("status_catalog", "statusCatalogId"); if (id is null) return; RespondeConDatos("getStatusCatalog", () => _dao.getStatusCatalog( new GetStatusCatalogRequest { StatusCatalogId = id.Value })); } [Fact] public void GetConsumptionHistory_Responde() { var id = _probe.Int("consumption_history", "consumptionHistoryId"); if (id is null) return; RespondeConDatos("getConsumptionHistory", () => _dao.getConsumptionHistory( new GetConsumptionHistoryRequest { ConsumptionHistoryId = id.Value })); } [Fact] public void GetTermsAndConditions_Responde() { var id = _probe.Int("terms_conditions", "idtermsConditions"); if (id is null) return; RespondeConDatos("getTermsandConditions", () => _dao.getTermsandConditions( new GetTermsAndConditionsRequest { IdTermsConditions = id.Value })); } [Fact] public void GetServicesToDeploy_Responde() { var status = _probe.Value("requests_queue", "status"); if (status is null) return; Responde("getServicesToDeploy", () => _dao.getServicesToDeploy( new GetServicesToDeployRequest { Status = status })); } [Fact] public void GetOrgVCD_Service_Responde() { var idservice = _probe.Int("services", "servicesId"); if (idservice is null) return; Responde("getOrgVCD_Service", () => _dao.getOrgVCD_Service( new GetOrgVCD_ServiceRequest { Idservice = idservice.Value })); } [Fact] public void GetContainerImage_Responde() { var idservice = _probe.Int("container_images", "servicesId"); if (idservice is null) return; RespondeConDatos("getContainerImage", () => _dao.getContainerImage( new GetContainerImageRequest { Idservice = idservice.Value })); } [Fact] public void GetFieldsVdc_Responde() { var vdcId = _probe.Int("vdc_vcd", "id"); if (vdcId is null) return; Responde("getfieldsvdc", () => _dao.getfieldsvdc( new CloudServiceDAO.getfieldsvdcRequest { VdcId = vdcId.Value })); } [Fact] public void GetGenericInformation_Responde() { // spskDy_tablefromclause arma un SELECT dinámico sobre la tabla y columna que se le pasen. var idregion = _probe.Value("region", "idregion"); if (idregion is null) return; RespondeConDatos("getGenericInformation", () => _dao.getGenericInformation(new GetGenericInformationRequest { TabName = "region", WcolNameFilter = "idregion", WParam = idregion })); } [Fact] public void StatusSupplyService_Responde() => Responde("StatusSupplyService", () => _dao.StatusSupplyService(new GetStatusSupplyRequest())); [Fact] public void ServiceStatusService_Responde() => Responde("ServiceStatusService", () => _dao.ServiceStatusService(new GetServiceStatusRequest())); // --------------------------------------------------------------- // administrationService // --------------------------------------------------------------- [Fact] public void GetAllMenuComponents_Responde() => Responde("getAllMenuComponents", () => _admin.getAllMenuComponents(new GetAllMenuComponentsRequest())); [Fact] public void GetUserAccessInfo_Responde() { var userId = _probe.Int("user_role", "iduser"); if (userId is null) return; Responde("getUserAccessInfo", () => _admin.getUserAccessInfo( new GetUserAccessInfoRequest { UserId = userId.Value })); } [Fact] public void GetRoleInfo_Responde() { var idrole = _probe.Int("role", "idrole"); if (idrole is null) return; Responde("getRoleInfo", () => _admin.getRoleInfo(new GetRoleInfoRequest { Idrole = idrole.Value })); } // --------------------------------------------------------------- // ProfileService // --------------------------------------------------------------- [Fact] public void Ping_Responde() => Responde("Ping", () => _profiles.Ping(new EmptyRequest())); [Fact] public void GetAllProfiles_Responde() => Responde("GetAllProfiles", () => _profiles.GetAllProfiles(new GetAllProfilesRequest())); [Fact] public void GetProfilesById_Responde() { var profileId = _probe.Int("profiles", "profileId"); if (profileId is null) return; RespondeConDatos("GetProfilesById", () => _profiles.GetProfilesById( new GetProfileByIdRequest { ProfileId = profileId.Value })); } [Fact] public void GetUserPermissions_Responde() { // Un usuario que sí tiene perfil asignado; con uno cualquiera la respuesta legítima // sería "sin permisos". var userId = _probe.Int("users_profiles", "userId"); if (userId is null) return; Responde("GetUserPermissions", () => _profiles.GetUserPermissions( new GetUserPermissionsRequest { UserId = userId.Value })); } // --------------------------------------------------------------- // CatalogService (Entity Framework, sin stored procedures) // --------------------------------------------------------------- [Fact] public void GetServicesCatalogEF_Responde() { var idProductType = _probe.Int("product_type", "id"); if (idProductType is null) return; Responde("GetServicesCatalogEF", () => _catalog.GetServicesCatalogEF( new EntityFrameworkDAOServices.GetServicesByProductTypeRequest { ProductTypeId = idProductType.Value })); } [Fact] public void GetAllowedProductTypes_Responde() { var userId = _probe.Int("user_vcd", "userVcdId"); if (userId is null) return; Responde("GetAllowedProductTypes", () => _catalog.GetAllowedProductTypes( new GetAllowedProductTypesRequest { UserId = userId.Value })); } [Fact] public void GetAllowedServices_Responde() { var userId = _probe.Int("user_vcd", "userVcdId"); if (userId is null) return; Responde("GetAllowedServices", () => _catalog.GetAllowedServices( new GetAllowedServicesRequest { UserId = userId.Value })); } // --------------------------------------------------------------- /// /// Para rpcs invocados sin sembrar parámetros, o cuyo resultado depende de joins que /// pueden legítimamente no producir filas. NotFound se acepta como respuesta válida. /// private static void Responde(string rpc, Func call) => Invoca(rpc, call, exigeDatos: false); /// /// Para rpcs que consultan una sola tabla con un id que acaba de /// leer de esa misma tabla. Aquí NotFound sí es un fallo: sabemos que la fila /// existe, así que "sin resultados" significa que la consulta está rota. /// /// /// Esta distinción importa porque la capa legacy atrapa las excepciones de MySQL y las /// convierte en NotFound: sin esta variante, un stored procedure inexistente se ve /// desde fuera igual que una tabla vacía. /// private static void RespondeConDatos(string rpc, Func call) => Invoca(rpc, call, exigeDatos: true); private static void Invoca(string rpc, Func call, bool exigeDatos) { try { Assert.NotNull(call()); } catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound) { if (!exigeDatos) return; Assert.Fail( $"'{rpc}' devolvió NotFound con un id que existe en su propia tabla. " + "O la consulta está rota, o el stored procedure no existe y la capa legacy " + $"convirtió el error en NotFound. Detalle: {ex.Status.Detail?.Trim()}"); } catch (RpcException ex) when (ex.StatusCode == StatusCode.Unimplemented) { Assert.Fail( $"'{rpc}' está declarado en el proto pero el servidor responde Unimplemented: " + "o no hay override en C#, o la imagen desplegada es anterior al proto."); } catch (RpcException ex) { Assert.Fail( $"'{rpc}' falló con {ex.StatusCode} — excepción no manejada en el servidor. " + $"Detalle: {ex.Status.Detail}"); } } }