using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Flaro.Api.Endpoints;
namespace Flaro.Api.Tests;
/// Ein abgelehnter Versuch sagt, warum — und zwar dort, wo gelesen wird.
///
///
///
/// Was hier schiefging, war unsichtbar. Der Server schrieb seine
/// deutschen Sätze in errors (bei ValidationProblem) oder in ein
/// selbstgebautes Feld fehler. Beide Clients lesen detail — und
/// nur das —, weil in title und errors sonst die englischen
/// Prüftexte des Rahmenwerks stehen. Also las man im Browser bei jedem
/// abgelehnten Versuch denselben Ersatzsatz: „Die Anfrage war so nicht zu
/// gebrauchen."
///
///
/// Auffallen konnte das nicht: die vorhandenen Tests prüften den Rumpf als
/// Zeichenkette (Does.Contain("eingeh")), und darin stand der Satz ja.
/// Nur eben an der falschen Stelle. Deshalb prüft dieser Test nicht, dass
/// ein Satz im Rumpf steht, sondern in welchem Feld.
///
///
public class FehlersatzTests
{
private static async Task<(HttpStatusCode Code, string? Detail)> Lesen(HttpResponseMessage antwort)
{
var roh = await antwort.Content.ReadAsStringAsync();
using var papier = JsonDocument.Parse(roh);
return (antwort.StatusCode,
papier.RootElement.TryGetProperty("detail", out var d) ? d.GetString() : null);
}
[Test]
public async Task Ein_zu_kurzes_Passwort_sagt_warum()
{
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var alsAdmin = server.AlsAngemeldeter(admin.Token);
var versuch = await alsAdmin.PostAsJsonAsync("/api/v1/users",
new CreateUserRequest("kurz", "1234", false));
var (code, detail) = await Lesen(versuch);
Assert.Multiple(() =>
{
Assert.That(code, Is.EqualTo(HttpStatusCode.BadRequest));
Assert.That(detail, Does.Contain("8 Zeichen"),
"Der Satz muss in `detail` stehen — nur das lesen die Clients.");
});
}
[Test]
public async Task Ein_vergebener_Name_sagt_welcher()
{
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var alsAdmin = server.AlsAngemeldeter(admin.Token);
await alsAdmin.PostAsJsonAsync("/api/v1/users",
new CreateUserRequest("doppelt", "einlangespasswort", false));
var zweites = await alsAdmin.PostAsJsonAsync("/api/v1/users",
new CreateUserRequest("doppelt", "einlangespasswort", false));
var (code, detail) = await Lesen(zweites);
Assert.Multiple(() =>
{
Assert.That(code, Is.EqualTo(HttpStatusCode.Conflict));
Assert.That(detail, Does.Contain("doppelt"));
});
}
/// Die zweite der beiden alten Formen — der selbstgebaute Rumpf.
[Test]
public async Task Eine_Meldung_ohne_Text_sagt_warum()
{
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var alsAdmin = server.AlsAngemeldeter(admin.Token);
// Die Kennung gibt es nicht — das ist hier gerade recht: auch dieser
// Satz ging früher den unsichtbaren Weg.
var versuch = await alsAdmin.PostAsJsonAsync(
$"/api/v1/items/{Guid.CreateVersion7()}/reports",
new MeldungRequest("bild", "Etwas stimmt nicht."));
var (code, detail) = await Lesen(versuch);
Assert.Multiple(() =>
{
Assert.That(code, Is.EqualTo(HttpStatusCode.BadRequest));
Assert.That(detail, Is.EqualTo("Diesen Titel gibt es nicht."));
});
}
/// Damit die beiden Formen nicht zurückkommen.
///
///
/// Ein Wächter am Quelltext, kein Test am Verhalten. Die nächste
/// Ablehnung wird irgendwann neu geschrieben, und dann liegt
/// Results.BadRequest(new { fehler = … }) näher als
/// — es sieht ja richtig aus. Ein Test, der
/// jede Ablehnung einzeln abklappert, wäre dagegen genau so vollständig wie
/// die Liste, die jemand pflegt. Dieselbe Art Wächter steht in
/// FreigabeTests über den Inhaltsrouten.
///
[Test]
public void Keine_Ablehnung_geht_mehr_an_detail_vorbei()
{
var wurzel = Quellordner();
var endpunkte = Directory.GetFiles(
Path.Combine(wurzel, "src", "Flaro.Api"), "*.cs", SearchOption.AllDirectories);
var ertappt = new List();
foreach (var datei in endpunkte)
{
if (Path.GetFileName(datei) is "Einwand.cs") continue;
var inhalt = File.ReadAllText(datei);
if (inhalt.Contains("ValidationProblem"))
ertappt.Add($"{Path.GetFileName(datei)}: ValidationProblem");
// Auf die Form, nicht auf das Wort: `fehler` heisst auch manche
// örtliche Variable, etwa im Ausnahmebehandler in `Program.cs`.
if (System.Text.RegularExpressions.Regex.IsMatch(inhalt, @"new\s*\{\s*fehler"))
ertappt.Add($"{Path.GetFileName(datei)}: new {{ fehler = … }}");
}
Assert.That(ertappt, Is.Empty,
"Diese Form legt den Satz in ein Feld, das kein Client liest. "
+ "Einwand.Bitte/Fehlt/Belegt schreibt ihn nach `detail`.");
}
/// Findet `server/` von den Testbinärdateien aus.
private static string Quellordner()
{
var ordner = new DirectoryInfo(AppContext.BaseDirectory);
while (ordner is not null && !Directory.Exists(Path.Combine(ordner.FullName, "src", "Flaro.Api")))
ordner = ordner.Parent;
Assert.That(ordner, Is.Not.Null, "Der Quellordner war von den Tests aus nicht zu finden.");
return ordner!.FullName;
}
}