using ClawdDotNet.Tools.DirectAPI; using Shouldly; namespace ClawdDotNet.Tools.Tests.DirectAPI; /// /// S3 aus der Bestandsaufnahme: Die abgerufene URL ging als "source" an das Modell — /// inklusive apikey= im Query-String. Der Schlüssel landete damit im Kontext, wurde /// bei jedem Folgeschritt erneut gesendet und in ChatContext.json geschrieben. /// public sealed class UrlSanitizerTests { private const string Secret = "GEHEIM-abc123XYZ"; [Theory] [InlineData("https://api.twelvedata.com/quote?symbol=NVDA&apikey=GEHEIM-abc123XYZ")] [InlineData("https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=X&apikey=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?api_key=GEHEIM-abc123XYZ&x=1")] [InlineData("https://api.example.com/v1?token=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?access_token=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?KEY=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?ApiKey=GEHEIM-abc123XYZ&other=2")] [InlineData("https://api.example.com/v1?secret=GEHEIM-abc123XYZ")] [InlineData("https://api.example.com/v1?a=1&apikey=GEHEIM-abc123XYZ#fragment")] public void Sensible_Parameter_werden_maskiert(string url) { var sanitized = UrlSanitizer.Sanitize(url); sanitized.ShouldNotContain(Secret); sanitized.ShouldContain("***"); } [Fact] public void Harmlose_Parameter_bleiben_lesbar() { var sanitized = UrlSanitizer.Sanitize( "https://api.twelvedata.com/quote?symbol=NVDA&interval=1day&apikey=GEHEIM-abc123XYZ"); sanitized.ShouldContain("symbol=NVDA"); sanitized.ShouldContain("interval=1day"); sanitized.ShouldContain("apikey=***"); } [Fact] public void Der_Pfad_bleibt_erhalten() { var sanitized = UrlSanitizer.Sanitize("https://api.massive.com/v2/aggs/ticker/NVDA?apikey=x"); sanitized.ShouldStartWith("https://api.massive.com/v2/aggs/ticker/NVDA?"); } [Fact] public void Ein_Fragment_bleibt_erhalten() { var sanitized = UrlSanitizer.Sanitize("https://x.de/a?apikey=geheim#abschnitt"); sanitized.ShouldEndWith("#abschnitt"); sanitized.ShouldNotContain("geheim"); } [Theory] [InlineData("https://api.example.com/v1")] [InlineData("https://api.example.com/v1?symbol=NVDA")] public void URLs_ohne_sensible_Parameter_bleiben_unveraendert(string url) { UrlSanitizer.Sanitize(url).ShouldBe(url); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void Leere_Eingaben_fuehren_nicht_zu_einem_Fehler(string? url) { Should.NotThrow(() => UrlSanitizer.Sanitize(url)); } [Fact] public void Ein_Parameter_ohne_Wert_stoert_nicht() { var sanitized = UrlSanitizer.Sanitize("https://x.de/a?flag&apikey=geheim"); sanitized.ShouldContain("flag"); sanitized.ShouldNotContain("geheim"); } [Fact] public void Ein_Schluessel_als_Teil_eines_anderen_Namens_wird_nicht_faelschlich_maskiert() { // "monkey" enthält "key", ist aber kein sensibler Parameter. var sanitized = UrlSanitizer.Sanitize("https://x.de/a?monkey=banane"); sanitized.ShouldBe("https://x.de/a?monkey=banane"); } }