using SatelliteProvider.TestSupport; namespace SatelliteProvider.IntegrationTests; class Program { static async Task Main(string[] args) { // AZ-492: perf-harness bootstrap subcommands short-circuit before any // HTTP / DB setup so they can be invoked from scripts/run-performance-tests.sh // on a host that only has the .NET SDK installed. if (args.Length > 0) { if (args[0].Equals("--mint-only", StringComparison.OrdinalIgnoreCase)) { return PerfBootstrap.MintToken(); } if (args[0].Equals("--gen-uav-fixture", StringComparison.OrdinalIgnoreCase)) { return PerfBootstrap.GenerateUavFixture(args); } } var apiUrl = Environment.GetEnvironmentVariable("API_URL") ?? "https://api:8080"; var modeEnv = Environment.GetEnvironmentVariable("INTEGRATION_TESTS_MODE")?.Trim().ToLowerInvariant(); var modeArg = args.FirstOrDefault(a => a.Equals("--smoke", StringComparison.OrdinalIgnoreCase) || a.Equals("--full", StringComparison.OrdinalIgnoreCase)); if (modeArg != null) { TestRunMode.Smoke = modeArg.Equals("--smoke", StringComparison.OrdinalIgnoreCase); } else if (!string.IsNullOrEmpty(modeEnv)) { TestRunMode.Smoke = modeEnv == "smoke"; } // AZ-493: opt-out of the startup DB reset so a developer can inspect leftover state. var keepStateEnv = Environment.GetEnvironmentVariable("INTEGRATION_KEEP_STATE")?.Trim(); var keepState = args.Any(a => a.Equals("--keep-state", StringComparison.OrdinalIgnoreCase)) || string.Equals(keepStateEnv, "1", StringComparison.Ordinal) || string.Equals(keepStateEnv, "true", StringComparison.OrdinalIgnoreCase); var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING") ?? "Host=postgres;Port=5432;Database=satelliteprovider;Username=postgres;Password=postgres"; string jwtSecret; string jwtIssuer; string jwtAudience; try { jwtSecret = JwtTestHelpers.ResolveSecretOrThrow(); jwtIssuer = JwtTestHelpers.ResolveIssuerOrThrow(); jwtAudience = JwtTestHelpers.ResolveAudienceOrThrow(); } catch (InvalidOperationException ex) { Console.WriteLine("❌ Integration tests cannot start: JWT configuration is incomplete."); Console.WriteLine($" {ex.Message}"); return 1; } Console.WriteLine("Starting Integration Tests"); Console.WriteLine("========================="); Console.WriteLine($"API URL : {apiUrl}"); Console.WriteLine($"Mode : {(TestRunMode.Smoke ? "smoke (fast subset, tightened timeouts)" : "full")}"); Console.WriteLine($"State : {(keepState ? "keep (DB reset skipped)" : "reset (clean DB at startup, AZ-493)")}"); Console.WriteLine($"Auth : JWT_SECRET resolved ({System.Text.Encoding.UTF8.GetByteCount(jwtSecret)} bytes); iss={jwtIssuer}; aud={jwtAudience}"); Console.WriteLine(); using var httpClient = new HttpClient { BaseAddress = new Uri(apiUrl), Timeout = TimeSpan.FromMinutes(15) }; var defaultToken = JwtTestHelpers.MintAuthenticated(jwtSecret); JwtTestHelpers.AttachDefaultAuthorization(httpClient, defaultToken); try { Console.WriteLine("Waiting for API to be ready..."); await WaitForApiReady(httpClient); Console.WriteLine("✓ API is ready"); if (keepState) { Console.WriteLine("⚠ --keep-state / INTEGRATION_KEEP_STATE set; skipping DB reset (AZ-493)"); } else { try { await new IntegrationTestDatabaseReset(connectionString).EnsureCleanStateAsync(); } catch (InvalidOperationException ex) { Console.WriteLine($"❌ DB reset refused: {ex.Message}"); return 1; } } Console.WriteLine(); await JwtIntegrationTests.RunAll(apiUrl, jwtSecret); await UavUploadTests.RunAll(apiUrl, jwtSecret); await Http2MultiplexingTests.RunAll(apiUrl, jwtSecret); if (TestRunMode.Smoke) { await RunSmokeSuite(httpClient, connectionString); } else { await RunFullSuite(httpClient, connectionString); } Console.WriteLine(); Console.WriteLine("========================="); Console.WriteLine("All tests completed successfully!"); return 0; } catch (Exception ex) { Console.WriteLine(); Console.WriteLine("❌ Integration tests failed"); Console.WriteLine($"Error: {ex.Message}"); Console.WriteLine($"Stack trace: {ex.StackTrace}"); return 1; } } static async Task RunSmokeSuite(HttpClient httpClient, string connectionString) { await TileTests.RunGetTileByLatLonTest(httpClient); await RegionTests.RunRegionProcessingTest_200m_Zoom18(httpClient); await BasicRouteTests.RunSimpleRouteTest(httpClient); await ExtendedRouteTests.RunRouteWithTilesZipTest(httpClient); await SecurityTests.RunAll(httpClient); await StubAndErrorContractTests.RunAll(httpClient); await IdempotentPostTests.RunAll(httpClient); await TileInventoryTests.RunAll(httpClient); await TileInventoryValidationTests.RunAll(httpClient); await LeafletPathIndexOnlyTests.RunAll(connectionString); await MigrationTests.RunAll(); } static async Task RunFullSuite(HttpClient httpClient, string connectionString) { await TileTests.RunGetTileByLatLonTest(httpClient); await RegionTests.RunRegionProcessingTest_200m_Zoom18(httpClient); await RegionTests.RunRegionProcessingTest_400m_Zoom17(httpClient); await RegionTests.RunRegionProcessingTest_500m_Zoom18(httpClient); await BasicRouteTests.RunSimpleRouteTest(httpClient); await BasicRouteTests.RunRouteWithRegionProcessingAndStitching(httpClient); await ExtendedRouteTests.RunRouteWithTilesZipTest(httpClient); await ComplexRouteTests.RunComplexRouteWithStitching(httpClient); await ComplexRouteTests.RunComplexRouteWithStitchingAndGeofences(httpClient); await ExtendedRouteTests.RunExtendedRouteEast(httpClient); await SecurityTests.RunAll(httpClient); await StubAndErrorContractTests.RunAll(httpClient); await IdempotentPostTests.RunAll(httpClient); await TileInventoryTests.RunAll(httpClient); await TileInventoryValidationTests.RunAll(httpClient); await LeafletPathIndexOnlyTests.RunAll(connectionString); await MigrationTests.RunAll(); } static async Task WaitForApiReady(HttpClient httpClient, int maxRetries = 30) { for (int i = 0; i < maxRetries; i++) { try { var response = await httpClient.GetAsync("/"); if (response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.NotFound) { return; } } catch { } Console.WriteLine($" Attempt {i + 1}/{maxRetries} - waiting 2 seconds..."); await Task.Delay(2000); } throw new Exception("API did not become ready in time"); } }