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);
}
}