using System.Text.Json; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using SameBeat.Infrastructure.BazaDanych; using Xunit; namespace SameBeat.Tests.Integration; /// /// ETAP 8 -- rozszerzony GET /api/health. Sprawdza zarowno przypadek "baza dostepna" (InMemory /// provider, jak w reszcie testow integracyjnych), jak i "baza niedostepna": tu celowo NIE mockujemy /// niczego -- podpinamy prawdziwy provider Npgsql wskazujacy na zamkniety port na localhost /// (127.0.0.1:1). Polaczenie TCP do zamknietego portu jest odrzucane przez system operacyjny niemal /// natychmiast (RST), wiec test zostaje szybki i offline, a jednoczesnie realnie przechodzi przez /// CanConnectAsync tak jak zrobiloby to prawdziwe zerwanie polaczenia z Postgresem. /// public class HealthControllerTests : IClassFixture> { private readonly WebApplicationFactory _fabryka; public HealthControllerTests(WebApplicationFactory fabryka) { _fabryka = fabryka; } private WebApplicationFactory ZbudujFabryke(bool bazaDostepna) { return _fabryka.WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); builder.ConfigureServices(services => { var deskryptorKontekstu = services.SingleOrDefault(s => s.ServiceType == typeof(DbContextOptions)); if (deskryptorKontekstu is not null) { services.Remove(deskryptorKontekstu); } if (bazaDostepna) { services.AddDbContext(opcje => opcje.UseInMemoryDatabase($"HealthControllerTests-{Guid.NewGuid()}")); } else { services.AddDbContext(opcje => opcje.UseNpgsql("Host=127.0.0.1;Port=1;Database=x;Username=x;Password=x;Timeout=2;Command Timeout=2")); } }); }); } [Fact] public async Task Health_GdyBazaDostepna_ZwracaStatusOkIDostepnaTrue() { var klient = ZbudujFabryke(bazaDostepna: true).CreateClient(); var odpowiedz = await klient.GetAsync("/api/health"); odpowiedz.EnsureSuccessStatusCode(); using var dokument = JsonDocument.Parse(await odpowiedz.Content.ReadAsStringAsync()); var glowny = dokument.RootElement; // Ksztalt z ETAPU 1 zostaje bez zmian. Assert.Equal("OK", glowny.GetProperty("status").GetString()); Assert.False(string.IsNullOrWhiteSpace(glowny.GetProperty("wersja").GetString())); Assert.True(glowny.TryGetProperty("czasUtc", out _)); var baza = glowny.GetProperty("baza"); Assert.True(baza.GetProperty("dostepna").GetBoolean()); Assert.Equal("OK", baza.GetProperty("status").GetString()); Assert.True(glowny.GetProperty("signalR").GetProperty("polaczeniaOtwarte").GetInt32() >= 0); Assert.True(glowny.GetProperty("uptime").GetProperty("sekundy").GetDouble() >= 0); } [Fact] public async Task Health_GdyBazaNiedostepna_NieWywalaEndpointuIZwracaCzytelnyStatus() { var klient = ZbudujFabryke(bazaDostepna: false).CreateClient(); var odpowiedz = await klient.GetAsync("/api/health"); // KLUCZOWE: mimo niedostepnej bazy endpoint nadal odpowiada 200, nie 500 -- health check nie // moze sam siebie wywalic tylko dlatego, ze zalezna od niego baza danych nie odpowiada. odpowiedz.EnsureSuccessStatusCode(); using var dokument = JsonDocument.Parse(await odpowiedz.Content.ReadAsStringAsync()); var glowny = dokument.RootElement; Assert.Equal("DEGRADED", glowny.GetProperty("status").GetString()); var baza = glowny.GetProperty("baza"); Assert.False(baza.GetProperty("dostepna").GetBoolean()); Assert.Equal("niedostepna", baza.GetProperty("status").GetString()); // Nie ujawniamy szczegolow (connection string, wyjatek) -- caly obiekt "baza" ma tylko te trzy pola. var polaBazy = new List(); foreach (var pole in baza.EnumerateObject()) { polaBazy.Add(pole.Name); } Assert.Equal(new[] { "dostepna", "status", "czasOdpowiedziMs" }, polaBazy); } }