- 
                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