using System.Net.Http.Json; using System.Text.Json; namespace SatelliteProvider.IntegrationTests; public static class RouteTests { private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; public static async Task RunSimpleRouteTest(HttpClient httpClient) { Console.WriteLine("Test: Create Simple Route with Two Points"); Console.WriteLine("-----------------------------------------"); var routeId = Guid.NewGuid(); var request = new CreateRouteRequest { Id = routeId, Name = "Simple Test Route", Description = "Test route with 2 points", RegionSizeMeters = 500.0, ZoomLevel = 18, Points = new List { new() { Latitude = 48.276067180586544, Longitude = 37.38445758819581 }, new() { Latitude = 48.27074009522731, Longitude = 37.374029159545906 } } }; Console.WriteLine($"Creating route with 2 points:"); Console.WriteLine($" Start: ({request.Points[0].Latitude}, {request.Points[0].Longitude})"); Console.WriteLine($" End: ({request.Points[1].Latitude}, {request.Points[1].Longitude})"); Console.WriteLine($" Region Size: {request.RegionSizeMeters}m"); Console.WriteLine($" Zoom Level: {request.ZoomLevel}"); Console.WriteLine(); var response = await httpClient.PostAsJsonAsync("/api/satellite/route", request); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(); throw new Exception($"API returned error status {response.StatusCode}: {errorContent}"); } var route = await response.Content.ReadFromJsonAsync(JsonOptions); if (route == null) { throw new Exception("No route data returned from API"); } Console.WriteLine("Route Details:"); Console.WriteLine($" ID: {route.Id}"); Console.WriteLine($" Name: {route.Name}"); Console.WriteLine($" Total Points: {route.TotalPoints}"); Console.WriteLine($" Total Distance: {route.TotalDistanceMeters:F2}m"); Console.WriteLine(); var startPoints = route.Points.Count(p => p.PointType == "start"); var endPoints = route.Points.Count(p => p.PointType == "end"); var intermediatePoints = route.Points.Count(p => p.PointType == "intermediate"); Console.WriteLine("Point Types:"); Console.WriteLine($" Start points: {startPoints}"); Console.WriteLine($" Intermediate points: {intermediatePoints}"); Console.WriteLine($" End points: {endPoints}"); Console.WriteLine(); if (startPoints != 1) { throw new Exception($"Expected 1 start point, got {startPoints}"); } if (endPoints != 1) { throw new Exception($"Expected 1 end point, got {endPoints}"); } Console.WriteLine("Point spacing validation:"); for (int i = 1; i < route.Points.Count; i++) { var point = route.Points[i]; if (point.DistanceFromPrevious.HasValue) { if (point.DistanceFromPrevious.Value > 200.0) { throw new Exception($"Point {i} is {point.DistanceFromPrevious.Value:F2}m from previous, exceeds 200m limit"); } Console.WriteLine($" Point {i} ({point.PointType}): {point.DistanceFromPrevious.Value:F2}m from previous"); } } Console.WriteLine(); Console.WriteLine("Retrieving route by ID..."); var getResponse = await httpClient.GetAsync($"/api/satellite/route/{routeId}"); if (!getResponse.IsSuccessStatusCode) { throw new Exception($"Failed to retrieve route: {getResponse.StatusCode}"); } var retrievedRoute = await getResponse.Content.ReadFromJsonAsync(JsonOptions); if (retrievedRoute == null || retrievedRoute.Id != routeId) { throw new Exception("Retrieved route does not match created route"); } Console.WriteLine($"✓ Route retrieved successfully"); Console.WriteLine($"✓ Retrieved {retrievedRoute.Points.Count} points"); Console.WriteLine(); Console.WriteLine("✓ Route created successfully"); Console.WriteLine("✓ All point spacing validated (≤ 200m)"); Console.WriteLine(); Console.WriteLine("Simple Route Test: PASSED"); } public static async Task RunRouteWithRegionProcessingAndStitching(HttpClient httpClient) { Console.WriteLine(); Console.WriteLine("Test: Route with Region Processing and Full Map Stitching (Service-Level)"); Console.WriteLine("==========================================================================="); Console.WriteLine(); var routeId = Guid.NewGuid(); var request = new CreateRouteRequest { Id = routeId, Name = "Route with Region Processing", Description = "Test route that processes regions for all points and stitches a full map", RegionSizeMeters = 300.0, ZoomLevel = 18, RequestMaps = true, Points = new List { new() { Latitude = 48.276067180586544, Longitude = 37.38445758819581 }, new() { Latitude = 48.27074009522731, Longitude = 37.374029159545906 } } }; Console.WriteLine("Step 1: Creating route with RequestMaps=true"); Console.WriteLine($" Start: ({request.Points[0].Latitude}, {request.Points[0].Longitude})"); Console.WriteLine($" End: ({request.Points[1].Latitude}, {request.Points[1].Longitude})"); Console.WriteLine($" Region Size: {request.RegionSizeMeters}m"); Console.WriteLine($" Zoom Level: {request.ZoomLevel}"); Console.WriteLine($" Request Maps: {request.RequestMaps}"); Console.WriteLine(); var routeResponse = await httpClient.PostAsJsonAsync("/api/satellite/route", request); if (!routeResponse.IsSuccessStatusCode) { var errorContent = await routeResponse.Content.ReadAsStringAsync(); throw new Exception($"Route creation failed: {routeResponse.StatusCode} - {errorContent}"); } var route = await routeResponse.Content.ReadFromJsonAsync(JsonOptions); if (route == null) { throw new Exception("No route data returned"); } Console.WriteLine($"✓ Route created with {route.TotalPoints} points"); Console.WriteLine($" Distance: {route.TotalDistanceMeters:F2}m"); Console.WriteLine($" Request Maps: {route.RequestMaps}"); Console.WriteLine($" Maps Ready: {route.MapsReady}"); Console.WriteLine(); if (!route.RequestMaps) { throw new Exception("Expected RequestMaps to be true"); } if (route.MapsReady) { throw new Exception("Expected MapsReady to be false initially"); } Console.WriteLine("Step 2: Waiting for route maps to be ready"); Console.WriteLine(" (Service is processing regions SEQUENTIALLY to avoid API throttling)"); Console.WriteLine(); RouteResponseModel? finalRoute = null; int maxAttempts = 180; int pollInterval = 3000; for (int attempt = 0; attempt < maxAttempts; attempt++) { await Task.Delay(pollInterval); var getResponse = await httpClient.GetAsync($"/api/satellite/route/{routeId}"); if (!getResponse.IsSuccessStatusCode) { throw new Exception($"Failed to get route status: {getResponse.StatusCode}"); } var currentRoute = await getResponse.Content.ReadFromJsonAsync(JsonOptions); if (currentRoute == null) { throw new Exception("No route returned"); } if (currentRoute.MapsReady) { finalRoute = currentRoute; Console.WriteLine($"✓ Route maps ready in approximately {attempt * pollInterval / 1000}s"); break; } if (attempt % 5 == 0) { Console.WriteLine($" Waiting... (attempt {attempt + 1}/{maxAttempts})"); } if (attempt == maxAttempts - 1) { throw new Exception($"Timeout: Route maps did not become ready in {maxAttempts * pollInterval / 1000}s"); } } if (finalRoute == null) { throw new Exception("Final route is null"); } Console.WriteLine(); Console.WriteLine("Step 3: Verifying generated files"); if (string.IsNullOrEmpty(finalRoute.CsvFilePath)) { throw new Exception("CSV file path is null or empty"); } if (string.IsNullOrEmpty(finalRoute.SummaryFilePath)) { throw new Exception("Summary file path is null or empty"); } if (string.IsNullOrEmpty(finalRoute.StitchedImagePath)) { throw new Exception("Stitched image path is null or empty"); } if (!File.Exists(finalRoute.CsvFilePath)) { throw new Exception($"CSV file not found: {finalRoute.CsvFilePath}"); } if (!File.Exists(finalRoute.SummaryFilePath)) { throw new Exception($"Summary file not found: {finalRoute.SummaryFilePath}"); } if (!File.Exists(finalRoute.StitchedImagePath)) { throw new Exception($"Stitched image not found: {finalRoute.StitchedImagePath}"); } var csvLines = await File.ReadAllLinesAsync(finalRoute.CsvFilePath); var uniqueTileCount = csvLines.Length - 1; var stitchedInfo = new FileInfo(finalRoute.StitchedImagePath); Console.WriteLine("Files Generated:"); Console.WriteLine($" ✓ CSV: {Path.GetFileName(finalRoute.CsvFilePath)} ({uniqueTileCount} tiles)"); Console.WriteLine($" ✓ Summary: {Path.GetFileName(finalRoute.SummaryFilePath)}"); Console.WriteLine($" ✓ Stitched Map: {Path.GetFileName(finalRoute.StitchedImagePath)} ({stitchedInfo.Length / 1024:F2} KB)"); Console.WriteLine(); Console.WriteLine("Route Summary:"); Console.WriteLine($" Route ID: {finalRoute.Id}"); Console.WriteLine($" Route Points: {finalRoute.TotalPoints}"); Console.WriteLine($" Distance: {finalRoute.TotalDistanceMeters:F2}m"); Console.WriteLine($" Unique Tiles: {uniqueTileCount}"); Console.WriteLine($" Maps Ready: {finalRoute.MapsReady}"); Console.WriteLine(); Console.WriteLine("✓ Route with Region Processing and Stitching Test: PASSED"); } public static async Task RunComplexRouteWithStitching(HttpClient httpClient) { Console.WriteLine(); Console.WriteLine("Test: Complex Route with 10 Points - Region Processing and Stitching"); Console.WriteLine("======================================================================="); Console.WriteLine(); var routeId = Guid.NewGuid(); var request = new CreateRouteRequest { Id = routeId, Name = "Complex Route with 10 Points", Description = "Test route with 10 action points for complex map stitching", RegionSizeMeters = 300.0, ZoomLevel = 18, RequestMaps = true, Points = new List { new() { Latitude = 48.276067180586544, Longitude = 37.38445758819581 }, new() { Latitude = 48.27074009522731, Longitude = 37.374029159545906 }, new() { Latitude = 48.263312668696855, Longitude = 37.37707614898682 }, new() { Latitude = 48.26539817051818, Longitude = 37.36587524414063 }, new() { Latitude = 48.25851283439989, Longitude = 37.35952377319337 }, new() { Latitude = 48.254426906081555, Longitude = 37.374801635742195 }, new() { Latitude = 48.25914140977405, Longitude = 37.39068031311036 }, new() { Latitude = 48.25354110233028, Longitude = 37.401752471923835 }, new() { Latitude = 48.25902712391726, Longitude = 37.416257858276374 }, new() { Latitude = 48.26828345053738, Longitude = 37.402009963989265 } } }; Console.WriteLine("Step 1: Creating complex route with RequestMaps=true"); Console.WriteLine($" Action Points: {request.Points.Count}"); Console.WriteLine($" Region Size: {request.RegionSizeMeters}m"); Console.WriteLine($" Zoom Level: {request.ZoomLevel}"); Console.WriteLine($" Request Maps: {request.RequestMaps}"); Console.WriteLine(); Console.WriteLine("Route Path:"); for (int i = 0; i < request.Points.Count; i++) { Console.WriteLine($" {i + 1}. ({request.Points[i].Latitude}, {request.Points[i].Longitude})"); } Console.WriteLine(); var routeResponse = await httpClient.PostAsJsonAsync("/api/satellite/route", request); if (!routeResponse.IsSuccessStatusCode) { var errorContent = await routeResponse.Content.ReadAsStringAsync(); throw new Exception($"Route creation failed: {routeResponse.StatusCode} - {errorContent}"); } var route = await routeResponse.Content.ReadFromJsonAsync(JsonOptions); if (route == null) { throw new Exception("No route data returned"); } Console.WriteLine($"✓ Route created with {route.TotalPoints} total points"); Console.WriteLine($" Distance: {route.TotalDistanceMeters:F2}m"); Console.WriteLine($" Request Maps: {route.RequestMaps}"); Console.WriteLine($" Maps Ready: {route.MapsReady}"); Console.WriteLine(); var startPoints = route.Points.Count(p => p.PointType == "start"); var endPoints = route.Points.Count(p => p.PointType == "end"); var actionPoints = route.Points.Count(p => p.PointType == "action"); var intermediatePoints = route.Points.Count(p => p.PointType == "intermediate"); Console.WriteLine("Point Type Distribution:"); Console.WriteLine($" Start: {startPoints}"); Console.WriteLine($" Action: {actionPoints}"); Console.WriteLine($" Intermediate: {intermediatePoints}"); Console.WriteLine($" End: {endPoints}"); Console.WriteLine(); if (startPoints != 1) { throw new Exception($"Expected 1 start point, got {startPoints}"); } if (endPoints != 1) { throw new Exception($"Expected 1 end point, got {endPoints}"); } if (actionPoints != 8) { throw new Exception($"Expected 8 action points (excluding start/end), got {actionPoints}"); } if (!route.RequestMaps) { throw new Exception("Expected RequestMaps to be true"); } if (route.MapsReady) { throw new Exception("Expected MapsReady to be false initially"); } Console.WriteLine("Step 2: Waiting for complex route maps to be ready"); Console.WriteLine(" (Processing 56 regions SEQUENTIALLY to avoid API throttling)"); Console.WriteLine(" (This will take several minutes as each region is processed one at a time)"); Console.WriteLine(); RouteResponseModel? finalRoute = null; int maxAttempts = 240; int pollInterval = 3000; for (int attempt = 0; attempt < maxAttempts; attempt++) { await Task.Delay(pollInterval); var getResponse = await httpClient.GetAsync($"/api/satellite/route/{routeId}"); if (!getResponse.IsSuccessStatusCode) { throw new Exception($"Failed to get route status: {getResponse.StatusCode}"); } var currentRoute = await getResponse.Content.ReadFromJsonAsync(JsonOptions); if (currentRoute == null) { throw new Exception("No route returned"); } if (currentRoute.MapsReady) { finalRoute = currentRoute; Console.WriteLine($"✓ Complex route maps ready in approximately {attempt * pollInterval / 1000}s"); break; } if (attempt % 10 == 0) { Console.WriteLine($" Waiting... (attempt {attempt + 1}/{maxAttempts})"); } if (attempt == maxAttempts - 1) { throw new Exception($"Timeout: Complex route maps did not become ready in {maxAttempts * pollInterval / 1000}s"); } } if (finalRoute == null) { throw new Exception("Final route is null"); } Console.WriteLine(); Console.WriteLine("Step 3: Verifying generated files"); if (string.IsNullOrEmpty(finalRoute.CsvFilePath)) { throw new Exception("CSV file path is null or empty"); } if (string.IsNullOrEmpty(finalRoute.SummaryFilePath)) { throw new Exception("Summary file path is null or empty"); } if (string.IsNullOrEmpty(finalRoute.StitchedImagePath)) { throw new Exception("Stitched image path is null or empty"); } if (!File.Exists(finalRoute.CsvFilePath)) { throw new Exception($"CSV file not found: {finalRoute.CsvFilePath}"); } if (!File.Exists(finalRoute.SummaryFilePath)) { throw new Exception($"Summary file not found: {finalRoute.SummaryFilePath}"); } if (!File.Exists(finalRoute.StitchedImagePath)) { throw new Exception($"Stitched image not found: {finalRoute.StitchedImagePath}"); } var csvLines = await File.ReadAllLinesAsync(finalRoute.CsvFilePath); var uniqueTileCount = csvLines.Length - 1; var stitchedInfo = new FileInfo(finalRoute.StitchedImagePath); Console.WriteLine("Files Generated:"); Console.WriteLine($" ✓ CSV: {Path.GetFileName(finalRoute.CsvFilePath)} ({uniqueTileCount} tiles)"); Console.WriteLine($" ✓ Summary: {Path.GetFileName(finalRoute.SummaryFilePath)}"); Console.WriteLine($" ✓ Stitched Map: {Path.GetFileName(finalRoute.StitchedImagePath)} ({stitchedInfo.Length / 1024:F2} KB)"); Console.WriteLine(); Console.WriteLine("Route Summary:"); Console.WriteLine($" Route ID: {finalRoute.Id}"); Console.WriteLine($" Total Points: {finalRoute.TotalPoints}"); Console.WriteLine($" Action Points: {actionPoints}"); Console.WriteLine($" Distance: {finalRoute.TotalDistanceMeters:F2}m"); Console.WriteLine($" Unique Tiles: {uniqueTileCount}"); Console.WriteLine($" Maps Ready: {finalRoute.MapsReady}"); Console.WriteLine(); if (uniqueTileCount < 10) { throw new Exception($"Expected at least 10 unique tiles for complex route, got {uniqueTileCount}"); } if (stitchedInfo.Length < 1024) { throw new Exception($"Stitched image seems too small: {stitchedInfo.Length} bytes"); } Console.WriteLine("✓ Complex Route with 10 Points Test: PASSED"); } public static async Task RunExtendedRouteEast(HttpClient httpClient) { Console.WriteLine(); Console.WriteLine("Test: Extended Route with 20 Points - 10km East"); Console.WriteLine("================================================="); Console.WriteLine(); var routeId = Guid.NewGuid(); var request = new CreateRouteRequest { Id = routeId, Name = "Extended Route with 20 Points (10km East)", Description = "Test route with 20 action points, located 10km east of the complex route", RegionSizeMeters = 300.0, ZoomLevel = 18, RequestMaps = true, Points = new List { new() { Latitude = 48.276067180586544, Longitude = 37.51945758819581 }, new() { Latitude = 48.27074009522731, Longitude = 37.509029159545906 }, new() { Latitude = 48.263312668696855, Longitude = 37.51207614898682 }, new() { Latitude = 48.26539817051818, Longitude = 37.50087524414063 }, new() { Latitude = 48.25851283439989, Longitude = 37.49452377319337 }, new() { Latitude = 48.254426906081555, Longitude = 37.509801635742195 }, new() { Latitude = 48.25914140977405, Longitude = 37.52568031311036 }, new() { Latitude = 48.25354110233028, Longitude = 37.536752471923835 }, new() { Latitude = 48.25902712391726, Longitude = 37.551257858276374 }, new() { Latitude = 48.26828345053738, Longitude = 37.537009963989265 }, new() { Latitude = 48.27421563182974, Longitude = 37.52345758819581 }, new() { Latitude = 48.26889854647051, Longitude = 37.513029159545906 }, new() { Latitude = 48.26147111993905, Longitude = 37.51607614898682 }, new() { Latitude = 48.26355662176038, Longitude = 37.50487524414063 }, new() { Latitude = 48.25667128564209, Longitude = 37.49852377319337 }, new() { Latitude = 48.25258535732375, Longitude = 37.513801635742195 }, new() { Latitude = 48.25729986101625, Longitude = 37.52968031311036 }, new() { Latitude = 48.25169955357248, Longitude = 37.540752471923835 }, new() { Latitude = 48.25718557515946, Longitude = 37.555257858276374 }, new() { Latitude = 48.26644190177958, Longitude = 37.541009963989265 } } }; Console.WriteLine("Step 1: Creating extended route with RequestMaps=true"); Console.WriteLine($" Action Points: {request.Points.Count}"); Console.WriteLine($" Region Size: {request.RegionSizeMeters}m"); Console.WriteLine($" Zoom Level: {request.ZoomLevel}"); Console.WriteLine($" Request Maps: {request.RequestMaps}"); Console.WriteLine($" Location: ~10km east of original complex route"); Console.WriteLine(); Console.WriteLine("Route Path (first 5 and last 5 points):"); for (int i = 0; i < Math.Min(5, request.Points.Count); i++) { Console.WriteLine($" {i + 1}. ({request.Points[i].Latitude}, {request.Points[i].Longitude})"); } Console.WriteLine(" ..."); for (int i = Math.Max(5, request.Points.Count - 5); i < request.Points.Count; i++) { Console.WriteLine($" {i + 1}. ({request.Points[i].Latitude}, {request.Points[i].Longitude})"); } Console.WriteLine(); var routeResponse = await httpClient.PostAsJsonAsync("/api/satellite/route", request); if (!routeResponse.IsSuccessStatusCode) { var errorContent = await routeResponse.Content.ReadAsStringAsync(); throw new Exception($"Route creation failed: {routeResponse.StatusCode} - {errorContent}"); } var route = await routeResponse.Content.ReadFromJsonAsync(JsonOptions); if (route == null) { throw new Exception("No route data returned"); } Console.WriteLine($"✓ Route created with {route.TotalPoints} total points"); Console.WriteLine($" Distance: {route.TotalDistanceMeters:F2}m"); Console.WriteLine($" Request Maps: {route.RequestMaps}"); Console.WriteLine($" Maps Ready: {route.MapsReady}"); Console.WriteLine(); var startPoints = route.Points.Count(p => p.PointType == "start"); var endPoints = route.Points.Count(p => p.PointType == "end"); var actionPoints = route.Points.Count(p => p.PointType == "action"); var intermediatePoints = route.Points.Count(p => p.PointType == "intermediate"); Console.WriteLine("Point Type Distribution:"); Console.WriteLine($" Start: {startPoints}"); Console.WriteLine($" Action: {actionPoints}"); Console.WriteLine($" Intermediate: {intermediatePoints}"); Console.WriteLine($" End: {endPoints}"); Console.WriteLine(); if (startPoints != 1) { throw new Exception($"Expected 1 start point, got {startPoints}"); } if (endPoints != 1) { throw new Exception($"Expected 1 end point, got {endPoints}"); } if (actionPoints != 18) { throw new Exception($"Expected 18 action points (excluding start/end), got {actionPoints}"); } if (!route.RequestMaps) { throw new Exception("Expected RequestMaps to be true"); } if (route.MapsReady) { throw new Exception("Expected MapsReady to be false initially"); } Console.WriteLine("Step 2: Waiting for extended route maps to be ready"); Console.WriteLine(" (Processing regions SEQUENTIALLY to avoid API throttling)"); Console.WriteLine(" (This will take several minutes for 20 action points)"); Console.WriteLine(); RouteResponseModel? finalRoute = null; int maxAttempts = 360; int pollInterval = 3000; for (int attempt = 0; attempt < maxAttempts; attempt++) { await Task.Delay(pollInterval); var getResponse = await httpClient.GetAsync($"/api/satellite/route/{routeId}"); if (!getResponse.IsSuccessStatusCode) { throw new Exception($"Failed to get route status: {getResponse.StatusCode}"); } var currentRoute = await getResponse.Content.ReadFromJsonAsync(JsonOptions); if (currentRoute == null) { throw new Exception("No route returned"); } if (currentRoute.MapsReady) { finalRoute = currentRoute; Console.WriteLine($"✓ Extended route maps ready in approximately {attempt * pollInterval / 1000}s"); break; } if (attempt % 10 == 0) { Console.WriteLine($" Waiting... (attempt {attempt + 1}/{maxAttempts})"); } if (attempt == maxAttempts - 1) { throw new Exception($"Timeout: Extended route maps did not become ready in {maxAttempts * pollInterval / 1000}s"); } } if (finalRoute == null) { throw new Exception("Final route is null"); } Console.WriteLine(); Console.WriteLine("Step 3: Verifying generated files"); if (string.IsNullOrEmpty(finalRoute.CsvFilePath)) { throw new Exception("CSV file path is null or empty"); } if (string.IsNullOrEmpty(finalRoute.SummaryFilePath)) { throw new Exception("Summary file path is null or empty"); } if (string.IsNullOrEmpty(finalRoute.StitchedImagePath)) { throw new Exception("Stitched image path is null or empty"); } if (!File.Exists(finalRoute.CsvFilePath)) { throw new Exception($"CSV file not found: {finalRoute.CsvFilePath}"); } if (!File.Exists(finalRoute.SummaryFilePath)) { throw new Exception($"Summary file not found: {finalRoute.SummaryFilePath}"); } if (!File.Exists(finalRoute.StitchedImagePath)) { throw new Exception($"Stitched image not found: {finalRoute.StitchedImagePath}"); } var csvLines = await File.ReadAllLinesAsync(finalRoute.CsvFilePath); var uniqueTileCount = csvLines.Length - 1; var stitchedInfo = new FileInfo(finalRoute.StitchedImagePath); Console.WriteLine("Files Generated:"); Console.WriteLine($" ✓ CSV: {Path.GetFileName(finalRoute.CsvFilePath)} ({uniqueTileCount} tiles)"); Console.WriteLine($" ✓ Summary: {Path.GetFileName(finalRoute.SummaryFilePath)}"); Console.WriteLine($" ✓ Stitched Map: {Path.GetFileName(finalRoute.StitchedImagePath)} ({stitchedInfo.Length / 1024:F2} KB)"); Console.WriteLine(); Console.WriteLine("Route Summary:"); Console.WriteLine($" Route ID: {finalRoute.Id}"); Console.WriteLine($" Total Points: {finalRoute.TotalPoints}"); Console.WriteLine($" Action Points: {actionPoints}"); Console.WriteLine($" Distance: {finalRoute.TotalDistanceMeters:F2}m"); Console.WriteLine($" Unique Tiles: {uniqueTileCount}"); Console.WriteLine($" Maps Ready: {finalRoute.MapsReady}"); Console.WriteLine(); if (uniqueTileCount < 20) { throw new Exception($"Expected at least 20 unique tiles for extended route, got {uniqueTileCount}"); } if (stitchedInfo.Length < 1024) { throw new Exception($"Stitched image seems too small: {stitchedInfo.Length} bytes"); } Console.WriteLine("✓ Extended Route with 20 Points (10km East) Test: PASSED"); } }