Freigabe-Hygiene (#631, #579, #609, #610) und JSON-Filter fuer WebFetch (#632)

#631: Derselbe Vorschlag (gleicher Agent, gleiches Werkzeug, gleiche Argumente unabhaengig
von Schluesselreihenfolge) wird nicht erneut angelegt - der Agent erfaehrt die bestehende
Nummer. Offene Vorschlaege verfallen nach 24 h (Status Expired).

#609: Werkzeuge koennen den Freigabeschluessel aus dem Inhalt bestimmen
(IAgentTool.StagingAktion). Database: SELECT/SHOW/DESCRIBE -> query, INSERT/UPDATE/DELETE
-> write, DDL -> schema; write und schema sind freigabepflichtig. Ausgefuehrt wird nach
Statement-Art, schreibende Statements melden die Zeilenzahl.

#579: Database-Vorpruefung (Zugriffsebene, Tabellenfreigabe, SqlGuard) - was das Werkzeug
ablehnen wuerde, kommt nicht mehr in die Freigabeliste.

#610: SqlGuard ueberspringt IF [NOT] EXISTS und erkennt DESCRIBE/DESC <tabelle>.

#632: WebFetch fetch mit filter liefert aus JSON/JSONP nur passende Eintraege; beim
Blaettern durch grosses JSON empfiehlt der Hinweis den filter.

Version 0.1.10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-18 18:51:06 +02:00
co-authored by Claude Opus 5
parent f5731c50f8
commit f74e49ba35
18 changed files with 727 additions and 29 deletions
@@ -0,0 +1,88 @@
using System.Text.Json;
using ClawdDotNet.Tools.WebFetch;
using Shouldly;
namespace ClawdDotNet.Tools.Tests.WebFetch;
/// <summary>
/// #632: Die DWD-Unwetterwarnungen (JSONP, ganz Deutschland) wurden abschnittsweise
/// vollstaendig gelesen - 160.000 Tokens fuer eine einzige Stadt. Mit filter kommt nur,
/// was passt.
/// </summary>
public sealed class JsonFilterTests
{
private static readonly Uri Quelle = new("https://www.dwd.de/DWD/warnungen/warnapp/json/warnings.json");
private static string DwdAehnlich(int fuellWarnungen = 0)
{
var fuell = string.Join(",", Enumerable.Range(1, fuellWarnungen).Select(i =>
$$"""{"regionName":"Kreis {{i}}","event":"WINDBÖEN","level":2,"start":1726500000000}"""));
return $$"""
warnWetter.loadWarnings({"time":1726650000000,"warnings":{
"111000000":[{"regionName":"Berlin","event":"STARKREGEN","level":3,"start":1726650000000}],
"105000000":[{"regionName":"Hamburg","event":"FROST","level":1,"start":1726650000000}{{(fuell.Length > 0 ? "," + fuell : "")}}]
},"vorabInformation":{},"copyright":"DWD"});
""";
}
[Fact]
public void JSONP_wird_ausgepackt_und_nur_die_passende_Warnung_geliefert()
{
var inhalt = WebInhalt.Aufbereiten(DwdAehnlich(), "application/javascript", Quelle, AbrufModus.Auto, null, filter: "berlin");
inhalt.Art.ShouldBe("json");
inhalt.Bereich.ShouldBe("gefiltert");
using var doc = JsonDocument.Parse(inhalt.Text);
doc.RootElement.GetProperty("treffer").GetInt32().ShouldBe(1);
var eintrag = doc.RootElement.GetProperty("eintraege")[0];
eintrag.GetProperty("pfad").GetString().ShouldBe("$.warnings.111000000[0]");
eintrag.GetProperty("wert").GetProperty("event").GetString().ShouldBe("STARKREGEN");
inhalt.Text.ShouldNotContain("Hamburg");
}
[Fact]
public void Ein_passender_Schluesselname_liefert_seinen_Wert()
{
var inhalt = WebInhalt.Aufbereiten(DwdAehnlich(), "application/json", Quelle, AbrufModus.Auto, null, filter: "1110");
inhalt.Text.ShouldContain("STARKREGEN");
inhalt.Text.ShouldNotContain("FROST");
}
[Fact]
public void Das_gefilterte_Ergebnis_ist_ein_Bruchteil_der_ganzen_Antwort()
{
var gross = DwdAehnlich(fuellWarnungen: 800);
var inhalt = WebInhalt.Aufbereiten(gross, "application/json", Quelle, AbrufModus.Auto, null, filter: "Berlin");
gross.Length.ShouldBeGreaterThan(60_000);
inhalt.Text.Length.ShouldBeLessThan(400);
inhalt.NaechsterOffset.ShouldBeNull();
}
[Fact]
public void Ohne_Treffer_sagt_der_Hinweis_das_ausdruecklich()
{
var inhalt = WebInhalt.Aufbereiten(DwdAehnlich(), "application/json", Quelle, AbrufModus.Auto, null, filter: "Muenchen");
inhalt.Hinweis.ShouldNotBeNull().ShouldContain("Keine Eintraege");
}
[Fact]
public void Kein_gueltiges_JSON_wird_ungefiltert_mit_Hinweis_geliefert()
{
var inhalt = WebInhalt.Aufbereiten("Das ist Text, kein JSON.", "text/plain", Quelle, AbrufModus.Auto, null, filter: "Berlin");
inhalt.Text.ShouldBe("Das ist Text, kein JSON.");
inhalt.Hinweis.ShouldNotBeNull().ShouldContain("kein gueltiges JSON");
}
[Fact]
public void Blaettert_ein_Agent_durch_grosses_JSON_empfiehlt_der_Hinweis_den_filter()
{
var inhalt = WebInhalt.Aufbereiten(DwdAehnlich(fuellWarnungen: 800), "application/json", Quelle, AbrufModus.Auto, null);
inhalt.NaechsterOffset.ShouldNotBeNull();
inhalt.Hinweis.ShouldNotBeNull().ShouldContain("filter");
}
}