Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve how expectations are presented #22

Merged
merged 5 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 186 additions & 22 deletions src/WireMockInspector/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using DynamicData;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ReactiveUI;
using RestEase;
using TextMateSharp.Internal.Grammars.Parser;
using WireMock.Admin.Mappings;
using WireMock.Admin.Requests;
using WireMock.Admin.Scenarios;
using WireMock.Admin.Settings;
using WireMock.Client;
using WireMock.Types;
using Formatting = Newtonsoft.Json.Formatting;

namespace WireMockInspector.ViewModels
{
Expand Down Expand Up @@ -288,7 +291,7 @@ public MainWindowViewModel()
},
Scenario = model.Scenario is {} scenarioName && enrichedScenarios.TryGetValue(scenarioName, out var scenario)? scenario with {CurrentTransitionId = mappingId, ThisMappingTransition = $"[{model.WhenStateIs}] -> [{model.SetStateTo}]"}: null
};
}).OfType<MappingViewModel>().OrderBy(x=>x.UpdatedOn);
}).OfType<MappingViewModel>().OrderByDescending(x=>x.UpdatedOn);
Mappings.AddRange(mappings);
MappingSearchTerm = string.Empty;
DataLoaded = true;
Expand Down Expand Up @@ -681,8 +684,6 @@ public int RequestTypeFilter
private static MappingDetails GetMappingDetails(RequestViewModel req, MappingModel expectations)
{
var isPerfectMatch = req.Raw.RequestMatchResult?.IsPerfectMatch == true;
var statusCode = req.Raw.Response?.StatusCode?.ToString()?? string.Empty;
var statusCodeFormatted = $"{statusCode} ({HttpStatusCodeToDescriptionConverter.TranslateStatusCode(statusCode)})";
return new MappingDetails
{
MatchingStatus = req.MatchingStatus,
Expand All @@ -703,7 +704,12 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Value = req.Raw.Request.ClientIP
},
Expectations = ExpectationsAsJson(expectations.Request?.ClientIP),
Expectations = CastAsModel<ClientIPModel>(expectations.Request?.ClientIP) switch
{
string s => new SimpleStringExpectations {Value = s},
ClientIPModel {Matchers: {} } cim => MapToRichExpectations(cim, cim.Matchers, cim.MatchOperator),
_ => MissingExpectations.Instance,
},
NoExpectations = expectations.Request?.ClientIP is null
},
new()
Expand All @@ -714,7 +720,11 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Value = req.Raw.Request.Method
},
Expectations = ExpectationsAsJson(expectations.Request?.Methods),
Expectations = expectations.Request?.Methods switch
{
string[] methods=> new SimpleStringExpectations {Value = string.Join(", ", methods)},
_ => MissingExpectations.Instance
},
NoExpectations = expectations.Request?.Methods is null
},
new()
Expand All @@ -725,7 +735,12 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Value = req.Raw.Request.Url
},
Expectations = ExpectationsAsJson(expectations.Request?.Url),
Expectations = CastAsModel<UrlModel>(expectations.Request?.Url) switch
{
string s => new SimpleStringExpectations {Value = s},
UrlModel {Matchers:{}} urlModel => MapToRichExpectations(urlModel, urlModel.Matchers, urlModel.MatchOperator),
_ => MissingExpectations.Instance
},
NoExpectations = expectations.Request?.Url is null
},
new()
Expand All @@ -736,7 +751,12 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Value = req.Raw.Request.Path
},
Expectations = ExpectationsAsJson(expectations.Request?.Path),
Expectations = CastAsModel<PathModel>(expectations.Request?.Path) switch
{
string s => new SimpleStringExpectations {Value = s},
PathModel {Matchers: { }} pathModel => MapToRichExpectations(pathModel, pathModel.Matchers, pathModel.MatchOperator),
_ => MissingExpectations.Instance
},
NoExpectations = expectations.Request?.Path is null
},
new()
Expand All @@ -747,7 +767,18 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Items = req.Raw.Request.Headers?.OrderBy(x=>x.Key).SelectMany(x=> x.Value.Select(v => new KeyValuePair<string,string>(x.Key, v))).ToList() ?? new List<KeyValuePair<string, string>>()
},
Expectations = ExpectationsAsJson(expectations.Request?.Headers),
Expectations = expectations.Request?.Headers switch
{
IList<HeaderModel> headers => new GridExpectations()
{
Items = headers.Select(x=> new GridExpectationItem
{
Name = x.Name,
Matchers = x.Matchers!=null ? MapToRichExpectations(x, x.Matchers.ToArray(), x.MatchOperator).Matchers: Array.Empty<ExpectationMatcher>()
}).ToList()
},
_ => MissingExpectations.Instance
},
NoExpectations = expectations.Request?.Headers is null
},
new()
Expand All @@ -769,7 +800,18 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Items = req.Raw.Request.Query?.OrderBy(x=>x.Key).SelectMany(x=> x.Value.Select(v => new KeyValuePair<string,string>(x.Key, v))).ToList() ?? new List<KeyValuePair<string, string>>(),
},
Expectations = ExpectationsAsJson(expectations.Request?.Params),
Expectations = expectations.Request?.Params switch
{
IList<ParamModel> paramModels => new GridExpectations()
{
Items = paramModels.Select(x=> new GridExpectationItem
{
Name = x.Name,
Matchers = x.Matchers!=null ? MapToRichExpectations(x, x.Matchers.ToArray(), null).Matchers: Array.Empty<ExpectationMatcher>()
}).ToList()
},
_ => MissingExpectations.Instance
},
NoExpectations = expectations.Request?.Params is null
},
new()
Expand All @@ -787,7 +829,7 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
_ => new MarkdownCode("plaintext", "")
}
},
Expectations = ExpectationsAsJson(expectations.Request?.Body),
Expectations = MapToRichExpectations(expectations.Request?.Body),
NoExpectations = expectations.Request?.Body is null
}
},
Expand All @@ -799,9 +841,13 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
Matched = isPerfectMatch,
ActualValue = new SimpleActualValue
{
Value = statusCodeFormatted
Value = FormatStatusCode(req.Raw.Response.StatusCode)
},
Expectations = ExpectationsAsJson(expectations.Response?.StatusCode?.ToString())
Expectations = expectations.Response?.StatusCode switch
{
not null => new SimpleStringExpectations(){Value = FormatStatusCode(expectations.Response?.StatusCode)},
_ => MissingExpectations.Instance
}
},
new ()
{
Expand All @@ -811,7 +857,19 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Items = req.Raw.Response?.Headers?.OrderBy(x=>x.Key).SelectMany(x=> x.Value.Select(v => new KeyValuePair<string,string>(x.Key, v))).ToList() ?? new List<KeyValuePair<string, string>>()
},
Expectations = ExpectationsAsJson(expectations.Response?.Headers)
Expectations = expectations.Response?.Headers switch
{
not null => new SimpleKeyValueExpectations
{
Items = expectations.Response?.Headers.OrderBy(x=>x.Key).SelectMany(x=> x.Value switch
{
string v=> new[]{new KeyValuePair<string,string>(x.Key, v)},
JArray vals => vals.ToObject<string[]>().Select(vv=> new KeyValuePair<string,string>(x.Key, vv)),
_ => Array.Empty<KeyValuePair<string, string>>()
} ).ToList() ?? new List<KeyValuePair<string, string>>()
},
_ => MissingExpectations.Instance
}
},
new ()
{
Expand All @@ -821,16 +879,119 @@ private static MappingDetails GetMappingDetails(RequestViewModel req, MappingMod
{
Value = GetActualForRequestBody(req)
},
Expectations = expectations.Response switch
Expectations = new RawExpectations()
{
Definition = expectations.Response switch
{
{Body: {} bodyResponse} => WrapBodyInMarkdown(bodyResponse),
{BodyAsJson: {} bodyAsJson} => new MarkdownCode("json", bodyAsJson.ToString()!),
{BodyAsBytes: {} bodyAsBytes} => new MarkdownCode("plaintext", bodyAsBytes.ToString()?? string.Empty),
{BodyAsFile: {} bodyAsFile} => new MarkdownCode("plaintext",bodyAsFile),
_ => new MarkdownCode("plaintext",string.Empty)
}
}
}
}
};
}

private static string FormatStatusCode(object? code)
{
var statusCode = code?.ToString() ?? string.Empty;
var statusCodeFormatted = $"{statusCode} ({HttpStatusCodeToDescriptionConverter.TranslateStatusCode(statusCode)})";
return statusCodeFormatted;
}

private static object CastAsModel<T>(object? input)
{
if (input is JObject o)
{
return o.ToObject<T>();
}

return input;
}

private static ExpectationsModel MapToRichExpectations(BodyModel? requestBody)
{
if (requestBody == null)
return MissingExpectations.Instance;

var matchers = requestBody.Matcher != null ? new[] {requestBody.Matcher!} : requestBody.Matchers ?? Array.Empty<MatcherModel>();

return MapToRichExpectations(requestBody, matchers, requestBody.MatchOperator);
}

private static RichExpectations MapToRichExpectations(object definition, IReadOnlyList<MatcherModel> matchers, string? matchOperator)
{
IEnumerable<string> GetPatterns(MatcherModel m)
{
if (string.IsNullOrWhiteSpace(m.Pattern.ToString()) == false)
{
yield return m.Pattern.ToString();
}
else
{
foreach (var pattern in m.Patterns)
{
if (string.IsNullOrWhiteSpace(pattern.ToString()) == false)
{
{Body: {} bodyResponse} => WrapBodyInMarkdown(bodyResponse),
{BodyAsJson: {} bodyAsJson} => new MarkdownCode("json", bodyAsJson.ToString()!),
{BodyAsBytes: {} bodyAsBytes} => new MarkdownCode("plaintext", bodyAsBytes.ToString()?? string.Empty),
{BodyAsFile: {} bodyAsFile} => new MarkdownCode("plaintext",bodyAsFile),
_ => new MarkdownCode("plaintext",string.Empty)
yield return pattern.ToString();
}
}
}
}

IEnumerable<string> GetTags(MatcherModel m)
{
yield return m.Name;

if (m.IgnoreCase == true)
{
yield return "Ignore case";
}
else
{
yield return "Case sensitive";
}

if (m.RejectOnMatch == true)
{
yield return "Reject on match";
}

if (m.Regex == true)
{
yield return "Regex";
}

if (string.IsNullOrWhiteSpace(m.MatchOperator) == false)
{
yield return $"Match operator: {m.MatchOperator}";
}
}

return new RichExpectations
{
Definition = AsMarkdownCode("json", JsonConvert.SerializeObject(definition, Formatting.Indented)),
Operator = matchOperator,
Matchers = matchers.Select(x => new ExpectationMatcher()
{
Attributes = new[]
{
new KeyValuePair<string, string>("Matcher", x.Name),
new KeyValuePair<string, string>("Reject on match", x.RejectOnMatch == true ? "✅" : "❌"),
new KeyValuePair<string, string>("Ignore case", x.IgnoreCase == true ? "✅" : "❌"),
new KeyValuePair<string, string>("Regex", x.Regex == true ? "✅" : "❌"),
new KeyValuePair<string, string>("Operator", x.MatchOperator?.ToString()),
}.Where(x => string.IsNullOrWhiteSpace(x.Value) == false).ToList(),
Tags = GetTags(x).ToList(),
Patterns = GetPatterns(x).Select(y => y.Trim() switch
{
var v when v.StartsWith("{") || v.StartsWith("[") => (Text) new MarkdownCode("json", y).TryToReformat(),
_ => (Text)new SimpleText(y)
} ).ToList()
}).ToList()
};
}

Expand Down Expand Up @@ -873,14 +1034,17 @@ public string RequestSearchTerm



private static MarkdownCode ExpectationsAsJson(object? data)
private static ExpectationsModel ExpectationsAsJson(object? data)
{
if (data == null)
{
return new MarkdownCode("plaintext", string.Empty);
return MissingExpectations.Instance;
}

return AsMarkdownCode("json", JsonConvert.SerializeObject(data, Formatting.Indented));
return new RawExpectations()
{
Definition = AsMarkdownCode("json", JsonConvert.SerializeObject(data, Formatting.Indented))
};
}

private static bool? IsMatched(RequestViewModel req, string rule)
Expand Down
Loading
Loading