Skip to content

Commit 50ba691

Browse files
Make mock and error-response URL matching case-insensitive (#1934)
* Initial plan * Make mock and error-response URL matching case-insensitive. Closes #1931 Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> * Apply remaining changes Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> * Fix locked restore by regenerating stale NuGet lock files Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> Co-authored-by: Waldek Mastykarz <waldek@mastykarz.nl>
1 parent 6b2ba70 commit 50ba691

6 files changed

Lines changed: 80 additions & 7 deletions

File tree

‎DevProxy.Integration.Tests/BehaviorPluginsIntegrationTests.cs‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,42 @@ public async Task GenericRandomError_Rate100_OverridesOriginWithConfiguredError(
6666
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
6767
}
6868

69+
[Theory]
70+
[InlineData("/Get")]
71+
[InlineData("/Ge*")]
72+
public async Task GenericRandomError_MatchesUrlCaseInsensitively(string errorPath)
73+
{
74+
await using var origin = await FakeOrigin.StartAsync();
75+
var urls = KestrelProxyHarness.BuildUrlsToWatch(origin.Host);
76+
77+
var config = PluginConfig.FromJson($$"""
78+
{
79+
"rate": 100,
80+
"errors": [
81+
{
82+
"request": { "url": "http://{{origin.Host}}{{errorPath}}" },
83+
"responses": [ { "statusCode": 503 } ]
84+
}
85+
]
86+
}
87+
""");
88+
var plugin = new GenericRandomErrorPlugin(
89+
SharedHttpClient,
90+
NullLogger<GenericRandomErrorPlugin>.Instance,
91+
urls,
92+
ProxyConfig,
93+
config);
94+
95+
await using var proxy = await KestrelProxyHarness.StartAsync(
96+
origin.Host, [plugin]);
97+
using var client = proxy.CreateHttpClient();
98+
99+
// urlsToWatch matching ignores case, so error URL matching must too.
100+
using var response = await client.GetAsync(new Uri($"http://{origin.Host}/get"));
101+
102+
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
103+
}
104+
69105
[Fact]
70106
public async Task GenericRandomError_Rate99_SometimesPassesThrough()
71107
{

‎DevProxy.Integration.Tests/ManipulationAndMockingIntegrationTests.cs‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,43 @@ public async Task MockResponse_ShortCircuitsWithConfiguredMock()
320320
Assert.Contains("mocked-by-test", body, StringComparison.Ordinal);
321321
}
322322

323+
[Theory]
324+
[InlineData("/Get")]
325+
[InlineData("/Ge*")]
326+
public async Task MockResponse_MatchesUrlCaseInsensitively(string mockPath)
327+
{
328+
await using var origin = await FakeOrigin.StartAsync();
329+
var urls = KestrelProxyHarness.BuildUrlsToWatch(origin.Host);
330+
331+
var config = PluginConfig.FromJson($$"""
332+
{
333+
"mocks": [
334+
{
335+
"request": { "url": "http://{{origin.Host}}{{mockPath}}", "method": "GET" },
336+
"response": { "statusCode": 201, "body": "mocked-by-test" }
337+
}
338+
]
339+
}
340+
""");
341+
var plugin = new MockResponsePlugin(
342+
SharedHttpClient,
343+
NullLogger<MockResponsePlugin>.Instance,
344+
urls,
345+
ProxyConfig,
346+
config);
347+
348+
await using var proxy = await KestrelProxyHarness.StartAsync(
349+
origin.Host, [plugin]);
350+
using var client = proxy.CreateHttpClient();
351+
352+
using var response = await client.GetAsync(new Uri($"http://{origin.Host}/get"));
353+
var body = await response.Content.ReadAsStringAsync();
354+
355+
// urlsToWatch matching ignores case, so mock URL matching must too.
356+
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
357+
Assert.Contains("mocked-by-test", body, StringComparison.Ordinal);
358+
}
359+
323360
[Fact]
324361
public async Task Auth_ApiKey_RejectsRequestWithoutKey()
325362
{

‎DevProxy.Plugins/Behavior/GenericRandomErrorPlugin.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private static ThrottlingInfo ShouldThrottle(IHttpRequest request, string thrott
188188
return false;
189189
}
190190

191-
if (errorResponse.Request.Url == request.Url &&
191+
if (string.Equals(errorResponse.Request.Url, request.Url, StringComparison.OrdinalIgnoreCase) &&
192192
HasMatchingBody(errorResponse, request))
193193
{
194194
return true;
@@ -203,7 +203,7 @@ private static ThrottlingInfo ShouldThrottle(IHttpRequest request, string thrott
203203

204204
// turn mock URL with wildcard into a regex and match against the request URL
205205
var errorResponseUrlRegex = Regex.Escape(errorResponse.Request.Url).Replace("\\*", ".*", StringComparison.OrdinalIgnoreCase);
206-
return Regex.IsMatch(request.Url, $"^{errorResponseUrlRegex}$") &&
206+
return Regex.IsMatch(request.Url, $"^{errorResponseUrlRegex}$", RegexOptions.IgnoreCase) &&
207207
HasMatchingBody(errorResponse, request);
208208
});
209209

‎DevProxy.Plugins/Mocking/GraphMockResponsePlugin.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ Configuration.Mocks is null ||
190190
var absoluteRequestFromBatchUrl = ProxyUtils
191191
.GetAbsoluteRequestUrlFromBatch(batchRequestUri, request.Url)
192192
.ToString();
193-
if (mockResponse.Request.Url == absoluteRequestFromBatchUrl)
193+
if (string.Equals(mockResponse.Request.Url, absoluteRequestFromBatchUrl, StringComparison.OrdinalIgnoreCase))
194194
{
195195
return true;
196196
}
@@ -204,7 +204,7 @@ Configuration.Mocks is null ||
204204

205205
//turn mock URL with wildcard into a regex and match against the request URL
206206
var mockResponseUrlRegex = Regex.Escape(mockResponse.Request.Url).Replace("\\*", ".*", StringComparison.OrdinalIgnoreCase);
207-
return Regex.IsMatch(absoluteRequestFromBatchUrl, $"^{mockResponseUrlRegex}$");
207+
return Regex.IsMatch(absoluteRequestFromBatchUrl, $"^{mockResponseUrlRegex}$", RegexOptions.IgnoreCase);
208208
});
209209
return mockResponse;
210210
}

‎DevProxy.Plugins/Mocking/MockResponsePlugin.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ Configuration.Mocks is null ||
304304
return false;
305305
}
306306

307-
if (mockResponse.Request.Url == request.Url &&
307+
if (string.Equals(mockResponse.Request.Url, request.Url, StringComparison.OrdinalIgnoreCase) &&
308308
HasMatchingBody(mockResponse, request) &&
309309
IsNthRequest(mockResponse))
310310
{
@@ -319,7 +319,7 @@ Configuration.Mocks is null ||
319319
}
320320

321321
// turn mock URL with wildcard into a regex and match against the request URL
322-
return Regex.IsMatch(request.Url, ProxyUtils.PatternToRegex(mockResponse.Request.Url)) &&
322+
return Regex.IsMatch(request.Url, ProxyUtils.PatternToRegex(mockResponse.Request.Url), RegexOptions.IgnoreCase) &&
323323
HasMatchingBody(mockResponse, request) &&
324324
IsNthRequest(mockResponse);
325325
});

‎DevProxy.Plugins/Mocking/WebSocketMockResponsePlugin.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ private static Task SendAsync(IWebSocketConnection connection, WebSocketMessageM
268268
return false;
269269
}
270270

271-
return Regex.IsMatch(requestUrl, ProxyUtils.PatternToRegex(mockUrl));
271+
return Regex.IsMatch(requestUrl, ProxyUtils.PatternToRegex(mockUrl), RegexOptions.IgnoreCase);
272272
});
273273
}
274274
}

0 commit comments

Comments
 (0)