Files
ClawdDotNet/tests/ClawdDotNet.Tools.Tests/DirectAPI/UrlSanitizerTests.cs
T
RichardandClaude Opus 5.5 77062facc9 Stufe 4c (#764): Bezeichner auf Englisch - Typen, Methoden, Felder, Parameter, Locals, Testnamen
Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt).
Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e):
Properties, Record-Parameter, Enum-Member sowie die Parameter der
Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder
anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:18:43 +02:00

96 lines
3.2 KiB
C#

using ClawdDotNet.Tools.DirectAPI;
using Shouldly;
namespace ClawdDotNet.Tools.Tests.DirectAPI;
/// <summary>
/// S3 from the inventory: the fetched URL went to the model as "source" —
/// including apikey= in the query string. The key thus ended up in the context, was
/// sent again at every following step and written into ChatContext.json.
/// </summary>
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 Sensitive_parameters_are_masked(string url)
{
var sanitized = UrlSanitizer.Sanitize(url);
sanitized.ShouldNotContain(Secret);
sanitized.ShouldContain("***");
}
[Fact]
public void Harmless_parameters_stay_readable()
{
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 The_path_is_kept()
{
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 A_fragment_is_kept()
{
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_without_sensitive_parameters_stay_unchanged(string url)
{
UrlSanitizer.Sanitize(url).ShouldBe(url);
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Empty_inputs_do_not_lead_to_an_error(string? url)
{
Should.NotThrow(() => UrlSanitizer.Sanitize(url));
}
[Fact]
public void A_parameter_without_value_does_no_harm()
{
var sanitized = UrlSanitizer.Sanitize("https://x.de/a?flag&apikey=geheim");
sanitized.ShouldContain("flag");
sanitized.ShouldNotContain("geheim");
}
[Fact]
public void A_key_as_part_of_another_name_is_not_wrongly_masked()
{
// "monkey" contains "key", but is not a sensitive parameter.
var sanitized = UrlSanitizer.Sanitize("https://x.de/a?monkey=banane");
sanitized.ShouldBe("https://x.de/a?monkey=banane");
}
}