Skip to content

Commit

Permalink
(feat) Adding scoring to route paths so wildcards are considered last
Browse files Browse the repository at this point in the history
  • Loading branch information
Lane Goolsby committed Sep 28, 2024
1 parent 4445bb7 commit 3cbe87e
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 27 deletions.
81 changes: 59 additions & 22 deletions src/Mockaco.AspNetCore/Middlewares/RequestMatchingMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

Expand Down Expand Up @@ -40,40 +36,81 @@ IOptions<MockacoOptions> options
return;
}

Mock bestMatch = null;
var bestScore = -1;

foreach (var mock in mockProvider.GetMocks())
{
if (await requestMatchers.AllAsync(_ => _.IsMatch(httpContext.Request, mock)))
if (await requestMatchers.AllAsync(e => e.IsMatch(httpContext.Request, mock)))
{
cache.Set($"{nameof(RequestMatchingMiddleware)} {httpContext.Request.Path.Value}",new
var score = ScoreRouteTemplate(mock.Route);

if (score > bestScore)
{
Route = httpContext.Request.Path.Value,
Timestamp = $"{DateTime.Now.ToString("t")}",
Headers = LoadHeaders(httpContext, options.Value.VerificationIgnoredHeaders),
Body = await httpContext.Request.ReadBodyStream()
}, DateTime.Now.AddMinutes(options.Value.MatchedRoutesCacheDuration));
bestMatch = mock;
bestScore = score;
}
}
else
{
_logger.LogDebug("Incoming request didn't match {mock}", mock);
}
}

if (bestMatch != null)
{
cache.Set($"{nameof(RequestMatchingMiddleware)} {httpContext.Request.Path.Value}", new
{
Route = httpContext.Request.Path.Value,
Timestamp = $"{DateTime.Now:t}",
Headers = LoadHeaders(httpContext, options.Value.VerificationIgnoredHeaders),
Body = await httpContext.Request.ReadBodyStream()
}, DateTime.Now.AddMinutes(options.Value.MatchedRoutesCacheDuration));

_logger.LogInformation("Incoming request matched {mock}", bestMatch);

await scriptContext.AttachRouteParameters(httpContext.Request, bestMatch);

var template = await templateTransformer.TransformAndSetVariables(bestMatch.RawTemplate, scriptContext);

_logger.LogInformation("Incoming request matched {mock}", mock);
mockacoContext.Mock = bestMatch;
mockacoContext.TransformedTemplate = template;

await scriptContext.AttachRouteParameters(httpContext.Request, mock);
await _next(httpContext);

var template = await templateTransformer.TransformAndSetVariables(mock.RawTemplate, scriptContext);
return;
}

mockacoContext.Mock = mock;
mockacoContext.TransformedTemplate = template;
_logger.LogInformation("Incoming request didn't match any mock");

await _next(httpContext);
mockacoContext.Errors.Add(new Error("Incoming request didn't match any mock"));
}

internal static int ScoreRouteTemplate(string route)
{
// Split the route into segments
var segments = route.Trim('/').Split('/', StringSplitOptions.RemoveEmptyEntries);

return;
var score = 0;
foreach (var segment in segments)
{
if (segment.StartsWith("{") && segment.EndsWith("}"))
{
//Wildcards get the lowest score
score++;
}
else
{
_logger.LogDebug("Incoming request didn't match {mock}", mock);
//Give more weight to static segments
score += 2;
}
}

_logger.LogInformation("Incoming request didn't match any mock");
//Give more weight to segments with multiple parameters
score *= 2;
}

mockacoContext.Errors.Add(new Error("Incoming request didn't match any mock"));
//Give more weight to longer routes
return score * segments.Length;
}

//TODO Remove redundant code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Xunit;

namespace Mockaco.Tests.Middlewares
{
Expand Down Expand Up @@ -58,5 +53,31 @@ public async Task Attaches_Request_Parameters_To_Be_Accessible_Via_ScriptContext
Moq.Mock.Verify(scriptContext);
}
}

[Theory]
[InlineData("{foo}", 2)]
[InlineData("/{foo}", 2)]
[InlineData("/{foo}/", 2)]
[InlineData("foo", 4)]
[InlineData("/foo", 4)]
[InlineData("foo/", 4)]
[InlineData("/foo/", 4)]
[InlineData("/{foo}/{bar}", 12)]
[InlineData("/{foo}/bar", 16)]
[InlineData("/foo/bar", 24)]
[InlineData("/{foo}/{bar}/{baz}", 42)]
[InlineData("/{foo}/{bar}/baz", 48)]
[InlineData("/{foo}/bar/{baz}", 54)]
[InlineData("/foo/{bar}/{baz}", 66)]
[InlineData("/{foo}/bar/baz", 60)]
[InlineData("/foo/bar/{baz}", 78)]
[InlineData("/foo/{bar}/baz", 72)]
[InlineData("/foo/bar/baz", 84)]
public void ScoreRouteTemplate(string routeTemplate, int expectedScore)
{
var result = RequestMatchingMiddleware.ScoreRouteTemplate(routeTemplate);

Assert.Equal(expectedScore, result);
}
}
}

0 comments on commit 3cbe87e

Please sign in to comment.