using System.Net.Http.Headers;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Halo.Api.Tests;
/// Ein frisch gestarteter Server mit leerer Datenbank.
///
///
/// Eine echte SQLite-Datei, kein Ersatz im Arbeitsspeicher. Damit läuft
/// auch das durch, worauf es beim ersten Start ankommt: Verzeichnisse anlegen,
/// Migration wandern lassen, Einrichtung anbieten. Genau diese Kette sieht ein
/// Fremder als Erstes — und genau sie würde ein Ersatzspeicher überspringen.
///
public sealed class HaloServer : IDisposable
{
private readonly WebApplicationFactory _grundlage;
private readonly WebApplicationFactory _fabrik;
private readonly string _verzeichnis;
public HttpClient Client { get; }
public HaloServer(Action? dienste = null)
{
_verzeichnis = Path.Combine(Path.GetTempPath(), "halo-test-" + Guid.CreateVersion7().ToString("N"));
Environment.SetEnvironmentVariable("HALO_DATA", Path.Combine(_verzeichnis, "config"));
Environment.SetEnvironmentVariable("HALO_CACHE", Path.Combine(_verzeichnis, "cache"));
Environment.SetEnvironmentVariable("HALO_TRANSCODE", Path.Combine(_verzeichnis, "transcode"));
// Die Zugriffsgrenze zählt je Adresse, und im Test kommt alles von
// derselben. Ohne das Hochsetzen prüfte man hier die Grenze statt der
// Anmeldung.
Environment.SetEnvironmentVariable("HALO_RATELIMIT_SIGNIN", "10000");
_grundlage = new WebApplicationFactory();
_fabrik = _grundlage.WithWebHostBuilder(b => b.ConfigureServices(sammlung =>
{
// **Ohne das dauert der ganze Lauf eine Viertelstunde.** Ein Host
// wartet beim Herunterfahren fünf Sekunden auf seine Dienste, und
// bei einem Testserver je Test summiert sich das auf mehr Zeit,
// als sämtliche Prüfungen zusammen brauchen. Hier gibt es nichts,
// worauf zu warten wäre.
sammlung.Configure(o => o.ShutdownTimeout = TimeSpan.FromMilliseconds(50));
dienste?.Invoke(sammlung);
}));
Client = _fabrik.CreateClient();
}
/// Richtet ein und meldet den Administrator an.
public async Task SetUpAndSignIn(string name = "guido", string passwort = "ein-langes-passwort")
{
var einrichten = await Client.PostAsJsonAsync("/api/v1/system/setup", new SetupRequest(name, passwort));
einrichten.EnsureSuccessStatusCode();
return await SignIn(name, passwort);
}
public async Task SignIn(string name, string passwort, string gerät = "iPhone", string kennung = "geraet-1")
{
var antwort = await Client.PostAsJsonAsync("/api/v1/auth/login",
new LoginRequest(name, passwort, gerät, kennung));
antwort.EnsureSuccessStatusCode();
return (await antwort.Content.ReadFromJsonAsync())!;
}
/// Ein eigener Client, der ein Token vorzeigt.
public HttpClient AlsAngemeldeter(string token)
{
var client = _fabrik.CreateClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
}
public void Dispose()
{
_fabrik.Dispose();
// Auch die Grundlage: `WithWebHostBuilder` gibt eine neue Fabrik
// zurück und lässt die alte stehen. Wer nur die neue entsorgt, häuft
// je Test einen Host an, der erst beim Prozessende abgeräumt wird.
_grundlage.Dispose();
try { Directory.Delete(_verzeichnis, recursive: true); } catch (IOException) { /* Aufräumen ist Kür */ }
}
}