Files
Oleksandr Bezdieniezhnykh 6b2c2d998e [AZ-577] [AZ-578] [AZ-579] [AZ-580] Implement E2E test batch 2
Adds 26 blackbox tests (FT-P-01..18, FT-N-01..08) covering full AC
matrices for Vehicles/Missions/Waypoints/Health/Errors. Three
spec-vs-code carry-forwards documented in batch_02_report.md and
pinned with [Trait("carry_forward", ...)].

Shared scaffolding: ApiDtos.cs, AssertProblemEnvelopeAsync helper,
Seeds.cs, StubSchema.cs, CascadeF3/F4 fixtures, PostgresStopStart
fixture (gated by COMPOSE_RESTART_ENABLED). Removes the 4 placeholder
Sanity.cs files (now superseded). docker-compose.test.yml gains the
expected_results volume mount + FIXTURE_SQL_DIR for the consumer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-15 08:28:37 +03:00

83 lines
3.0 KiB
C#

using System.Net;
using System.Net.Http;
using System.Text.Json;
using Azaion.Missions.E2E.Fixtures;
using Azaion.Missions.E2E.Helpers;
using Xunit;
namespace Azaion.Missions.E2E.Tests.Health;
/// <summary>
/// FT-P-16 (anonymous 200) and FT-P-17 (200 with PG stopped). FT-P-17 is a
/// SkippableFact: it runs only when COMPOSE_RESTART_ENABLED=1 and the e2e
/// container has docker CLI access; otherwise it skips with a clear reason.
/// Traces: AC-7.1, AC-7.2, AC-7.3.
/// </summary>
[Collection("Health")]
[Trait("Category", "Blackbox")]
public sealed class HealthTests : TestBase, IClassFixture<PostgresStopStartFixture>
{
private readonly PostgresStopStartFixture _pg;
public HealthTests(PostgresStopStartFixture pg) => _pg = pg;
[Fact]
[Trait("Traces", "AC-7.1")]
[Trait("max_ms", "2000")]
public async Task FT_P_16_health_returns_200_anonymous_with_lowercase_status_key()
{
// Arrange
using var http = new HttpRequestMessage(HttpMethod.Get, "/health");
// Explicitly NO Authorization header — health is anonymous.
// Act
using var response = await Missions.SendAsync(http);
// Assert
await HttpAssertions.AssertStatusAsync(response, HttpStatusCode.OK);
var raw = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(raw);
var root = doc.RootElement;
// The anonymous-object literal in Program.cs declares the key as
// lowercase "status"; assert that exact contract — a future global
// PascalCase shift would break consumers.
Assert.True(root.TryGetProperty("status", out var statusEl), $"missing 'status' key: {raw}");
Assert.Equal("healthy", statusEl.GetString());
// Reject any extra keys to pin the envelope.
var extras = root.EnumerateObject().Select(p => p.Name)
.Where(n => n != "status").ToArray();
Assert.True(extras.Length == 0,
$"unexpected extra keys in /health body: {string.Join(",", extras)}");
}
[SkippableFact]
[Trait("Traces", "AC-7.2,AC-7.3")]
[Trait("max_ms", "5000")]
public async Task FT_P_17_health_returns_200_with_postgres_stopped_proves_no_db_ping()
{
Skip.IfNot(_pg.Enabled,
"PostgresStopStartFixture disabled (COMPOSE_RESTART_ENABLED!=1). " +
"Enable in CI; locally this scenario requires docker socket access.");
// Arrange
_pg.Stop();
try
{
using var http = new HttpRequestMessage(HttpMethod.Get, "/health");
// Act
using var response = await Missions.SendAsync(http);
// Assert
await HttpAssertions.AssertStatusAsync(response, HttpStatusCode.OK);
var raw = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(raw);
Assert.Equal("healthy", doc.RootElement.GetProperty("status").GetString());
}
finally
{
_pg.Start();
}
}
}