using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using Azaion.Missions.E2E.Fixtures; using Azaion.Missions.E2E.Helpers; using Xunit; namespace Azaion.Missions.E2E.Tests.Vehicles; /// /// FT-P-01..06 — vehicle happy-path scenarios from /// _docs/02_document/tests/blackbox-tests.md § Positive. /// Traces: AC-1.1 / AC-1.2 / AC-1.4 / AC-1.5 / AC-1.6 / AC-1.10. /// [Collection("Vehicles")] [Trait("Category", "Blackbox")] [Trait("db_access", "seed-or-assert-only")] public sealed class PositiveTests : TestBase, IClassFixture { [Fact] [Trait("Traces", "AC-1.1")] [Trait("max_ms", "5000")] public async Task FT_P_01_create_non_default_returns_201_with_pascal_case_body() { // Arrange DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel); var token = await Tokens.MintDefaultAsync(); var request = new { Type = 0, Model = "Bayraktar", Name = "BR-01", FuelType = 1, BatteryCapacity = 0, EngineConsumption = 5, EngineConsumptionIdle = 1, IsDefault = false }; // Act using var http = new HttpRequestMessage(HttpMethod.Post, "/vehicles") { Content = JsonContent.Create(request) }; http.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Jwt); using var response = await Missions.SendAsync(http); // Assert await HttpAssertions.AssertStatusAsync(response, HttpStatusCode.Created); var raw = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(raw); var root = doc.RootElement; // Pin PascalCase contract — a future global camelCase migration must // break this test (results_report.md row 1.1 + AC-8.1). Assert.True(root.TryGetProperty("Id", out var idEl), $"body missing PascalCase 'Id': {raw}"); Assert.True(root.TryGetProperty("Name", out var nameEl)); Assert.True(root.TryGetProperty("IsDefault", out var defEl)); Assert.False(root.TryGetProperty("id", out _), "body unexpectedly camelCase"); var id = idEl.GetGuid(); Assert.Equal("BR-01", nameEl.GetString()); Assert.False(defEl.GetBoolean()); var count = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE id = @id", ("id", id)); Assert.Equal(1, count); } [Fact] [Trait("Traces", "AC-1.2")] [Trait("max_ms", "5000")] public async Task FT_P_02_create_default_demotes_prior_default() { // Arrange DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel); Seeds.Apply(Seeds.OneDefaultVehicle.Sql); var priorDefaultId = Seeds.OneDefaultVehicle.Id; var token = await Tokens.MintDefaultAsync(); var request = new { Type = 0, Model = "Bayraktar", Name = "BR-02-default", FuelType = 1, BatteryCapacity = 0, EngineConsumption = 5, EngineConsumptionIdle = 1, IsDefault = true }; // Act using var http = new HttpRequestMessage(HttpMethod.Post, "/vehicles") { Content = JsonContent.Create(request) }; http.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Jwt); using var response = await Missions.SendAsync(http); // Assert await HttpAssertions.AssertStatusAsync(response, HttpStatusCode.Created); var newVehicle = await response.Content.ReadFromJsonAsync() ?? throw new InvalidOperationException("response body deserialized to null"); Assert.True(newVehicle.IsDefault, "newly-created vehicle must be default"); var totalDefaults = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE is_default = TRUE"); Assert.Equal(1, totalDefaults); var priorIsDefault = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE id = @id AND is_default = TRUE", ("id", priorDefaultId)); Assert.Equal(0, priorIsDefault); var newIsDefault = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE id = @id AND is_default = TRUE", ("id", newVehicle.Id)); Assert.Equal(1, newIsDefault); } [Fact] [Trait("Traces", "AC-1.4")] [Trait("max_ms", "5000")] [Trait("carry_forward", "setDefault-route-method-return")] public async Task FT_P_03_setDefault_promotes_existing_vehicle() { // CARRY-FORWARD: the canonical task spec + results_report.md row 1.4 say // "POST /vehicles/{id}/setDefault" returning "200 with body {Vehicle}", // but the actual code (Controllers/VehiclesController.cs:48) is // "[HttpPatch("{id:guid}/default")]" returning "204 NoContent" (no body). // Per /autodev batch 2 user choice, this test asserts the CODE shape. // When the spec/code divergence is closed, flip method+status here. // Arrange DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel); Seeds.Apply(Seeds.OneDefaultVehicle.Sql); var priorDefaultId = Seeds.OneDefaultVehicle.Id; var p2Id = Guid.NewGuid(); Seeds.Apply($""" INSERT INTO vehicles (id, type, model, name, fuel_type, battery_capacity, engine_consumption, engine_consumption_idle, is_default) VALUES ('{p2Id}', 0, 'Bayraktar', 'BR-promote', 1, 0, 5, 1, false); """); var token = await Tokens.MintDefaultAsync(); // Act using var http = new HttpRequestMessage(HttpMethod.Patch, $"/vehicles/{p2Id}/default") { Content = JsonContent.Create(new { IsDefault = true }) }; http.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Jwt); using var response = await Missions.SendAsync(http); // Assert await HttpAssertions.AssertStatusAsync(response, HttpStatusCode.NoContent); var promoted = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE id = @id AND is_default = TRUE", ("id", p2Id)); Assert.Equal(1, promoted); var demoted = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE id = @id AND is_default = TRUE", ("id", priorDefaultId)); Assert.Equal(0, demoted); DbAssertions.AssertExactlyOneDefaultVehicle(); } [Fact] [Trait("Traces", "AC-1.5")] [Trait("max_ms", "2000")] public async Task FT_P_04_list_is_unpaginated_array_ordered_by_name() { // Arrange DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel); Seeds.Apply(Seeds.Three_BR01_BR02_MQ9.Sql); var token = await Tokens.MintDefaultAsync(); // Act using var http = new HttpRequestMessage(HttpMethod.Get, "/vehicles"); http.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Jwt); 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(JsonValueKind.Array, doc.RootElement.ValueKind); var vehicles = JsonSerializer.Deserialize>(raw) ?? throw new InvalidOperationException($"could not deserialize array: {raw}"); Assert.Equal(3, vehicles.Count); Assert.Equal(new[] { "BR-01", "BR-02", "MQ-9" }, vehicles.Select(v => v.Name).ToArray()); } [Fact] [Trait("Traces", "AC-1.6")] [Trait("max_ms", "2000")] public async Task FT_P_05_filter_is_case_insensitive_for_both_casings() { // Arrange DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel); Seeds.Apply(Seeds.Three_BR01_BR02_MQ9.Sql); var token = await Tokens.MintDefaultAsync(); async Task> FetchAsync(string query) { using var http = new HttpRequestMessage(HttpMethod.Get, "/vehicles?" + query); http.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Jwt); using var resp = await Missions.SendAsync(http); await HttpAssertions.AssertStatusAsync(resp, HttpStatusCode.OK); return await resp.Content.ReadFromJsonAsync>() ?? throw new InvalidOperationException("null body for /vehicles filter"); } // Act var upper = await FetchAsync("name=BR&isDefault=true"); var lower = await FetchAsync("name=br&isDefault=true"); // Assert Assert.Single(upper); Assert.Equal("BR-01", upper[0].Name); Assert.Single(lower); Assert.Equal("BR-01", lower[0].Name); } [Fact] [Trait("Traces", "AC-1.10")] [Trait("max_ms", "2000")] public async Task FT_P_06_delete_with_no_references_returns_204_and_row_is_gone() { // Arrange DbResetFixture.ResetDatabase(TestEnvironment.DbSideChannel); Seeds.Apply(Seeds.OneDefaultVehicle.Sql); var id = Seeds.OneDefaultVehicle.Id; var token = await Tokens.MintDefaultAsync(); // Act using var http = new HttpRequestMessage(HttpMethod.Delete, $"/vehicles/{id}"); 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); var remaining = DbAssertions.ScalarCount( "SELECT COUNT(*) FROM vehicles WHERE id = @id", ("id", id)); Assert.Equal(0, remaining); } }