mirror of
https://github.com/azaion/missions.git
synced 2026-06-22 22:41:08 +00:00
[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>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using Azaion.Missions.E2E.Fixtures;
|
||||
using Azaion.Missions.E2E.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Azaion.Missions.E2E.Tests.Missions;
|
||||
|
||||
/// <summary>
|
||||
/// FT-P-12 — mission cascade delete walks every dependency table.
|
||||
/// Owns its own xUnit collection (<c>CascadeF3</c>) because the F3 fixture
|
||||
/// is destructive and must run with a fresh DB per scenario.
|
||||
/// Compares per-table counts against
|
||||
/// <c>_docs/00_problem/input_data/expected_results/cascade_F3_walk.json</c>
|
||||
/// via deep JSON diff (results_report.md row 3.1).
|
||||
/// </summary>
|
||||
[Collection("CascadeF3")]
|
||||
[Trait("Category", "Blackbox")]
|
||||
[Trait("db_access", "seed-or-assert-only")]
|
||||
public sealed class CascadeF3Tests : TestBase, IClassFixture<CascadeF3Fixture>
|
||||
{
|
||||
public CascadeF3Tests(CascadeF3Fixture _) { /* fixture seeds the DB. */ }
|
||||
|
||||
[Fact]
|
||||
[Trait("Traces", "AC-3.1")]
|
||||
[Trait("max_ms", "10000")]
|
||||
public async Task FT_P_12_mission_cascade_walks_every_dependency_table()
|
||||
{
|
||||
// Arrange — load the canonical walk JSON to assert pre-state and post-state.
|
||||
var walkJson = JsonDocument.Parse(File.ReadAllText(
|
||||
Path.Combine(
|
||||
Environment.GetEnvironmentVariable("FIXTURE_SQL_DIR") ?? "/app/fixtures",
|
||||
"..", // expected_results/.. == input_data
|
||||
"expected_results",
|
||||
"cascade_F3_walk.json")));
|
||||
var preState = walkJson.RootElement.GetProperty("expected_per_table_pre_state_for_safety_check");
|
||||
|
||||
// Refresh the F3 fixture into a known state — IClassFixture seeds once
|
||||
// per class, but we want a clean walk for this single scenario.
|
||||
DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel);
|
||||
StubSchema.EnsureCreated();
|
||||
Seeds.Apply(FixtureSql.Load("fixture_cascade_F3"));
|
||||
|
||||
// Sanity-check the pre-state — if the seed fixture failed silently, the
|
||||
// post-state assertions would trivially pass and mask the failure.
|
||||
Assert.Equal(preState.GetProperty("missions").GetInt32(),
|
||||
(int)DbAssertions.TableRowCount("missions"));
|
||||
Assert.Equal(preState.GetProperty("waypoints").GetInt32(),
|
||||
(int)DbAssertions.TableRowCount("waypoints"));
|
||||
Assert.Equal(preState.GetProperty("map_objects").GetInt32(),
|
||||
(int)DbAssertions.TableRowCount("map_objects"));
|
||||
Assert.Equal(preState.GetProperty("media").GetInt32(),
|
||||
(int)DbAssertions.TableRowCount("media"));
|
||||
Assert.Equal(preState.GetProperty("annotations").GetInt32(),
|
||||
(int)DbAssertions.TableRowCount("annotations"));
|
||||
Assert.Equal(preState.GetProperty("detection").GetInt32(),
|
||||
(int)DbAssertions.TableRowCount("detection"));
|
||||
|
||||
var token = await Tokens.MintDefaultAsync();
|
||||
|
||||
// Act
|
||||
using var http = new HttpRequestMessage(
|
||||
HttpMethod.Delete, $"/missions/{CascadeF3Fixture.MissionId}");
|
||||
http.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Jwt);
|
||||
using var response = await Missions.SendAsync(http);
|
||||
|
||||
// Assert
|
||||
await HttpAssertions.AssertStatusAsync(response, HttpStatusCode.NoContent);
|
||||
var bodyLength = (await response.Content.ReadAsByteArrayAsync()).Length;
|
||||
Assert.Equal(0, bodyLength);
|
||||
|
||||
// The walk JSON pins per-table post-state filters; assert each one.
|
||||
var postState = walkJson.RootElement.GetProperty("expected_per_table_post_state");
|
||||
AssertCount("missions", "id = '22222222-0000-0000-0000-000000000001'", 0);
|
||||
AssertCount("waypoints", "mission_id = '22222222-0000-0000-0000-000000000001'", 0);
|
||||
AssertCount("map_objects", "mission_id = '22222222-0000-0000-0000-000000000001'", 0);
|
||||
AssertCount("media", "id IN ('media-fixture-001', 'media-fixture-002')", 0);
|
||||
AssertCount("annotations", "id IN ('anno-fixture-001', 'anno-fixture-002')", 0);
|
||||
AssertCount("detection", "annotation_id IN ('anno-fixture-001', 'anno-fixture-002')", 0);
|
||||
|
||||
// Sanity: the walk JSON has the same expectations we just asserted — fail
|
||||
// loudly if the JSON is out of sync with the in-source filters.
|
||||
Assert.Equal(0, postState.GetProperty("missions").GetProperty("expected_count").GetInt32());
|
||||
}
|
||||
|
||||
private static void AssertCount(string table, string filterSql, long expected)
|
||||
{
|
||||
if (!table.All(c => char.IsLetterOrDigit(c) || c == '_'))
|
||||
throw new ArgumentException($"unsafe table identifier '{table}'", nameof(table));
|
||||
var actual = DbAssertions.ScalarCount($"SELECT COUNT(*) FROM {table} WHERE {filterSql}");
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user