using System.Net.Http.Json; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using OtaFleet.Api.Data; using OtaFleet.Api.Endpoints; namespace OtaFleet.Tests; public class VehicleTests : IClassFixture> { private readonly WebApplicationFactory _factory; public VehicleTests(WebApplicationFactory factory) { _factory = factory.WithWebHostBuilder(builder => { builder.ConfigureServices(services => { var dbContextDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); if (dbContextDescriptor != null) services.Remove(dbContextDescriptor); var dbConnectionDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(System.Data.Common.DbConnection)); if (dbConnectionDescriptor != null) services.Remove(dbConnectionDescriptor); services.AddSingleton(container => { var connection = new Microsoft.Data.Sqlite.SqliteConnection("DataSource=:memory:"); connection.Open(); return connection; }); services.AddDbContext((container, options) => { var connection = container.GetRequiredService(); options.UseSqlite(connection); }); }); }); } [Fact] public async Task Register_ShouldReturnVehicle() { var client = _factory.CreateClient(); var request = new RegisterVehicleRequest("TEST_VIN_001", "1.0.0"); var response = await client.PostAsJsonAsync("/api/vehicles/register", request); response.EnsureSuccessStatusCode(); var vehicle = await response.Content.ReadFromJsonAsync(); Assert.NotNull(vehicle); Assert.Equal("TEST_VIN_001", vehicle.Vin); Assert.Equal("Online", vehicle.Status); } [Fact] public async Task GetVehicles_WithGroup_ShouldNotThrowObjectCycle() { var client = _factory.CreateClient(); // 1. Create a Group var groupRes = await client.PostAsJsonAsync("/api/admin/groups", new OtaFleet.Api.Endpoints.CreateGroupRequest("CycleTestGroup", "Testing Cycles")); groupRes.EnsureSuccessStatusCode(); var group = await groupRes.Content.ReadFromJsonAsync(); Assert.NotNull(group); // 2. Register a Vehicle await client.PostAsJsonAsync("/api/vehicles/register", new RegisterVehicleRequest("CYCLE_VIN", "1.0")); // 3. Assign Vehicle to Group (This creates the cycle: Vehicle -> Group -> Vehicles -> Vehicle ...) await client.PostAsJsonAsync($"/api/admin/groups/{group.Id}/vehicles", new OtaFleet.Api.Endpoints.AssignVehicleRequest("CYCLE_VIN")); // 4. Fetch Vehicles (Should serialize the cycle safely) var response = await client.GetAsync("/api/admin/vehicles"); response.EnsureSuccessStatusCode(); var vehicles = await response.Content.ReadFromJsonAsync>(); Assert.NotEmpty(vehicles); var v = vehicles.First(x => x.Vin == "CYCLE_VIN"); Assert.NotNull(v.Group); } [Fact] public async Task GetVehicles_ShouldReturnCorrectCount() { var client = _factory.CreateClient(); int vehicleCount = 10; for (int i = 0; i < vehicleCount; i++) { await client.PostAsJsonAsync("/api/vehicles/register", new RegisterVehicleRequest($"MULTI_VIN_{i}", "1.0.0")); } var response = await client.GetAsync("/api/admin/vehicles"); response.EnsureSuccessStatusCode(); var vehicles = await response.Content.ReadFromJsonAsync>(); Assert.NotNull(vehicles); // Note: Other tests might have added vehicles, so we check for at least our count Assert.True(vehicles.Count >= vehicleCount); Assert.Contains(vehicles, v => v.Vin == "MULTI_VIN_0"); Assert.Contains(vehicles, v => v.Vin == $"MULTI_VIN_{vehicleCount - 1}"); } [Fact] public async Task LoadTest_ConcurrentRegistrations() { var client = _factory.CreateClient(); int requestCount = 100; var tasks = new List>(); for (int i = 0; i < requestCount; i++) { // Capture index for closure int index = i; tasks.Add(Task.Run(() => client.PostAsJsonAsync("/api/vehicles/register", new RegisterVehicleRequest($"LOAD_VIN_{index}", "1.0.0")))); } var results = await Task.WhenAll(tasks); foreach (var result in results) { result.EnsureSuccessStatusCode(); } var response = await client.GetAsync("/api/admin/vehicles"); var vehicles = await response.Content.ReadFromJsonAsync>(); Assert.NotNull(vehicles); Assert.True(vehicles.Count(v => v.Vin.StartsWith("LOAD_VIN_")) == requestCount); } }