-
Notifications
You must be signed in to change notification settings - Fork 40
Description
Prerequisites
- I have written a descriptive issue title
- I have searched existing issues to ensure a similar issue has not already been created
Description
If you have multiple mocks setup that are similar, but one is fully formed while the others have wildcards (i.e. {}'s), Mockaco returns the first match it finds, not the mock with the best match.
The order in which Mockaco searches appears to be file-name driven. Meaning it will match a.json before z.json, even if z.json has a fully formed route whereas a.json has wildcards.
For example:
a.json
route: /api/{foo}/{bar}
b.json
route: /api/path/{bar}
c.json
route: /api/{foo}/file
z.json
route: /api/path/file
When calling /api/path/file Mockaco currently matches a.json, b.json, or c.json first because their file names are first alphabetically even though z.json is a complete match.
Proposed solution
Mockaco should look at all defined route matches and choose the route with the most complete match, not the first match it finds.
I propose changing this logic to look something like this:
//snip
Mock bestMatch = null;
var bestScore = - 1;
foreach (var mock in mockProvider.GetMocks())
{
if (await requestMatchers.AllAsync(e => e.IsMatch(httpContext.Request, mock)))
{
var score = ScoreRouteTemplate(mock.Route);
if (score > bestScore)
{
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);
mockacoContext.Mock = bestMatch;
mockacoContext.TransformedTemplate = template;
await _next(httpContext);
return;
}
//snip
private int ScoreRouteTemplate(string routeTemplate)
{
var segments = routeTemplate.Split('/');
int score = 0;
foreach (var segment in segments)
{
if (segment.StartsWith("{") && segment.EndsWith("}"))
{
// This is a wildcard or route parameter, lower score
score += 1;
}
else
{
// Explicit path segment, higher score
score += 10;
}
}
return score;
}
Note that ScoreRouteTemplate doesn't really handle b.json and c.json in the problem description. It would need a little more work to rank the higher cardinality paths (i.e. /api/foo/{bar}) higher than lower cardinatity (i.e. /api/{foo}/bar). Before I spend more time on it I wanted to get feedback on the approach.
Alternatives
I've tried using file names in creative ways but its becoming difficult to manage.
Additional context
No response