Files
satellite-provider/SatelliteProvider.IntegrationTests/Program.cs
T
Oleksandr Bezdieniezhnykh 1d89cd9997
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-353][AZ-354][AZ-356] Refactor 03 batch 2: harden API surface
AZ-353: Centralize 500 handling via GlobalExceptionHandler /
AddProblemDetails / UseExceptionHandler. Sanitized ProblemDetails
body carries a generic title, RFC9110 type link, and the request's
TraceIdentifier as correlationId; the leaky exception message stays
server-side in the ERR log entry. Strip per-endpoint
try/catch (Exception) wrappers and the unused ILogger<Program>
parameters they served. Preserve the typed ArgumentException catch
in CreateRoute (AC-3). The handler maps BadHttpRequestException
back to its framework-supplied StatusCode so model-binding /
malformed-body failures stay 4xx instead of being promoted to 500.

AZ-354: Extract CorsConfigurationValidator (pure static helpers)
and wire it into Program.cs. Production with empty
CorsConfig:AllowedOrigins and no CorsConfig:AllowAnyOrigin opt-in
now throws InvalidOperationException at host startup. Development
keeps the permissive default but logs a warning post-build. Adds
the explicit CorsConfig:AllowAnyOrigin escape hatch.

AZ-356: GetSatelliteTilesByMgrs and UploadImage now return
Results.Problem(StatusCode 501) with ProblemDetails. Added
.ProducesProblem(501) so swagger.json documents the
not-implemented status.

Tests: SatelliteProvider.Tests now references SatelliteProvider.Api
(downward, idiomatic) so unit tests can reach the new helpers.
+9 CorsConfigurationValidator unit tests, +3
GlobalExceptionHandler unit tests, +3 StubAndErrorContractTests
integration tests (added to smoke + full suites).

58/58 unit + 5/5 smoke + 3/3 stub-contract pass.
Code review verdict: PASS.
Batch report: _docs/03_implementation/batch_08_report.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-10 23:52:52 +03:00

115 lines
4.2 KiB
C#

namespace SatelliteProvider.IntegrationTests;
class Program
{
static async Task<int> Main(string[] args)
{
var apiUrl = Environment.GetEnvironmentVariable("API_URL") ?? "http://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";
}
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();
using var httpClient = new HttpClient
{
BaseAddress = new Uri(apiUrl),
Timeout = TimeSpan.FromMinutes(15)
};
try
{
Console.WriteLine("Waiting for API to be ready...");
await WaitForApiReady(httpClient);
Console.WriteLine("✓ API is ready");
Console.WriteLine();
if (TestRunMode.Smoke)
{
await RunSmokeSuite(httpClient);
}
else
{
await RunFullSuite(httpClient);
}
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)
{
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);
}
static async Task RunFullSuite(HttpClient httpClient)
{
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);
}
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");
}
}