using System.Reflection; using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using RutasDBUS.Components.Pages; using RutasDBUS.Modelos.Historial; using RutasDBUS.Modelos.Planificacion; using RutasDBUS.Servicios.Caminata; using RutasDBUS.Servicios.Configuracion; using RutasDBUS.Servicios.Planificacion; static void Check(bool result, string message) { if (!result) throw new Exception(message); Console.WriteLine("OK " + message); } var baseMethod = typeof(PlanificadorRutas).GetMethod("CrearConfiguracionBaseHistorial", BindingFlags.Static | BindingFlags.NonPublic)!; var defaults = (ConfiguracionHistorialRuta)baseMethod.Invoke(null, null)!; Check(defaults.PesoTransbordoRankingMinutos == 6, "Penalizacion inicial de 6 minutos"); var folder = Path.Combine(Path.GetTempPath(), "rutasdbus-regression-" + Guid.NewGuid().ToString("N")); IConfiguration Config() => new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["ValoresFabrica:Directorio"] = folder }).Build(); ServicioValoresFabrica Service() => new(Config(), NullLogger.Instance); try { defaults.PesoTransbordoRankingMinutos = 9; defaults.FechaSimulada = null; defaults.HoraSimulada = null; Directory.CreateDirectory(folder); var file = Path.Combine(folder, "configuracion-fabrica.json"); await File.WriteAllTextAsync(file, JsonSerializer.Serialize(defaults)); var loaded = await Service().CargarAsync(); Check(loaded?.PesoTransbordoRankingMinutos == 9, "Valores compartidos entre instancias"); Check(loaded is { FechaSimulada: null, HoraSimulada: null }, "Los valores de fabrica no fijan la fecha y hora"); File.WriteAllText(file, "{incompleto"); Check(await Service().CargarAsync() is null, "JSON danado permite usar valores base"); } finally { // Solo datos temporales creados por esta prueba. if (Directory.Exists(folder)) Directory.Delete(folder, recursive: true); } var fake = new FakeValhalla(); var planner = new PlanificadorRutasServicio(null!, null!, fake, null!, null!, null!, null!); var walk = typeof(PlanificadorRutasServicio).GetMethod("WalkValhallaCached", BindingFlags.Instance | BindingFlags.NonPublic)!; async Task<(double[,]? linea, double distancia, double duracion)> Walk(List>? zones = null) => await (Task<(double[,]?, double, double)>)walk.Invoke(planner, new object?[] { 43.31, -1.99, 43.32, -1.98, zones, null })!; List Ring(double lon, double lat, double size) => new() { new[] { lon, lat }, new[] { lon + size, lat }, new[] { lon + size, lat + size }, new[] { lon, lat + size }, new[] { lon, lat } }; await Walk(); var far = new List> { Ring(-2.3, 43.0, .01) }; await Walk(far); Check(fake.Calls == 2 && fake.Zones == 1, "Toda zona activa se envia a V2 aunque este lejos de la ruta previa"); var near = new List> { Ring(-1.985, 43.315, .001), Ring(-1.982, 43.317, .001) }; await Walk(near); Check(fake.Zones == 2, "Se envian todos los poligonos"); await Walk(near); Check(fake.Calls == 3, "Mismas zonas reutilizan cache restringida"); near[0][0][0] += .00001; await Walk(near); Check(fake.Calls == 4, "Mover un vertice invalida esa entrada de cache"); await Walk(new() { Ring(-2.2, 43.1, .5) }); Check(fake.Calls == 5, "Una zona que rodea la ruta no se ignora aunque sus vertices esten lejos"); fake.Fail = true; var retry = new List> { Ring(-1.984, 43.312, .0002) }; await Walk(retry); fake.Fail = false; await Walk(retry); Check(fake.Calls == 7, "Un fallo temporal no queda guardado en cache de zonas"); var puntos = new List { new[] { 43.314, -1.984 }, new[] { 43.315, -1.983 } }; await (Task<(double[,]?, double, double)>)walk.Invoke(planner, new object?[] { 43.31, -1.99, 43.32, -1.98, near, puntos })!; Check(fake.Zones == 2 && fake.Points == 2, "Se envian juntos todos los poligonos y puntos activos"); var page = new PlanificadorRutas(); var flags = BindingFlags.Instance | BindingFlags.NonPublic; var options = (List)typeof(PlanificadorRutas).GetField("_opcionesRuta", flags)!.GetValue(page)!; var selected = typeof(PlanificadorRutas).GetField("_indiceAlternativaSeleccionada", flags)!; var remember = typeof(PlanificadorRutas).GetMethod("ObtenerProveedorSoloAPieParaRecalculo", flags)!; string? Remember(bool keep) => (string?)remember.Invoke(page, new object[] { keep }); options.Add(new() { EsSoloAPie = true, ProveedorRuta = "v1" }); options.Add(new() { EsSoloAPie = true, ProveedorRuta = "v2" }); selected.SetValue(page, 1); Check(Remember(true) == "v2", "Se conserva la version andando seleccionada"); options.Clear(); Check(Remember(true) == "v2", "Otro cambio de punto durante el calculo conserva v2"); options.Add(new() { EsSoloAPie = false }); selected.SetValue(page, 0); Check(Remember(true) is null, "Seleccionar bus libera la preferencia andando"); options.Clear(); Check(Remember(false) is null, "Nueva seleccion de criterio no hereda la preferencia andando"); Console.WriteLine("Pruebas completadas."); sealed class FakeValhalla : IClienteValhalla { public Task ObtenerInformacionSueloAsync( double[,] linea, CancellationToken cancellationToken = default) => Task.FromResult(null); public int Calls { get; private set; } public int Zones { get; private set; } public int Points { get; private set; } public bool Fail { get; set; } public Task<(double[,]? Linea, double DistanciaMetros, double DuracionSegundos)> ObtenerRutaAsync( double latitudDesde, double longitudDesde, double latitudHasta, double longitudHasta, string costing = "pedestrian", List>? excludePolygons = null, List? excludeLocations = null) { Calls++; Zones = excludePolygons?.Count ?? 0; Points = excludeLocations?.Count ?? 0; return Task.FromResult<(double[,]?, double, double)>(Fail ? (null, 0, 0) : (new[,] { { latitudDesde, longitudDesde }, { latitudHasta, longitudHasta } }, 100, 100)); } }