Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
localhost
]
},
requestContentLength: 0,
responseHeaders: {
Content-Type: [
text/plain
Expand All @@ -81,7 +82,24 @@
},
contentType: text/plain,
contentLength: 13,
bodyPreview: Hello, World!
bodyPreview: Hello, World!,
route: {
Request: {
Methods: [
{
Method: GET
}
],
AbsoluteUri: https://example.com/index.txt
},
Responses: [
{
ContentResource: Example.Index.txt,
ContentType: text/plain,
StatusCode: 200
}
]
}
}
]
}
70 changes: 70 additions & 0 deletions src/Devlead.Testing.MockHttp.Tool.Tests/InspectTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,62 @@ public async Task InspectPageReturnsHtml()
Assert.That(html, Does.Contain("MockHttp inspect"));
Assert.That(html, Does.Contain("Request headers"));
Assert.That(html, Does.Contain("Response headers"));
Assert.That(html, Does.Contain("exchange"));
Assert.That(html, Does.Contain("highlightJson"));
Assert.That(html, Does.Contain("/favicon.ico"));
Assert.That(html, Does.Contain("https://www.nuget.org/packages/Devlead.Testing.MockHttp.Tool"));
Assert.That(html, Does.Contain("id=\"reqRoute\""));
Assert.That(html, Does.Contain("id=\"copyRoute\""));
}

[Test]
public async Task FaviconReturnsPng()
{
await using var host = await MockHttpTestHost.StartAsync();

var response = await host.Client.GetAsync("/favicon.ico");

Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
Assert.That(response.Content.Headers.ContentType?.MediaType, Is.EqualTo("image/png"));
var bytes = await response.Content.ReadAsByteArrayAsync();
Assert.That(bytes[0], Is.EqualTo(0x89));
Assert.That(bytes[1], Is.EqualTo((byte)'P'));
Assert.That(bytes[2], Is.EqualTo((byte)'N'));
Assert.That(bytes[3], Is.EqualTo((byte)'G'));
}

[Test]
public async Task InspectApiAfterPostIncludesRequestBody()
{
await using var host = await MockHttpTestHost.StartAsync();
using var content = new StringContent(
"""{"requestId":"dev-send","responses":[]}""",
System.Text.Encoding.UTF8,
"application/json");

await host.Client.PostAsync("/messaging/send", content);
var snapshot = await host.Client.GetFromJsonAsync<JsonElement>("/inspect/api");
var activity = snapshot.GetProperty("activity")[0];

Assert.That(activity.GetProperty("method").GetString(), Is.EqualTo("POST"));
Assert.That(activity.GetProperty("requestContentType").GetString(), Does.Contain("application/json"));
Assert.That(activity.GetProperty("requestBodyPreview").GetString(), Does.Contain("dev-send"));
Assert.That(activity.GetProperty("requestContentLength").GetInt32(), Is.GreaterThan(0));
}

[Test]
public async Task InspectApiAfterFormPostIncludesRequestBody()
{
await using var host = await MockHttpTestHost.StartAsync();
using var content = new StringContent("q=a+b", System.Text.Encoding.ASCII, "application/x-www-form-urlencoded");

await host.Client.PostAsync("/form", content);
var snapshot = await host.Client.GetFromJsonAsync<JsonElement>("/inspect/api");
var activity = snapshot.GetProperty("activity")[0];

Assert.That(activity.GetProperty("requestContentType").GetString(), Does.Contain("application/x-www-form-urlencoded"));
Assert.That(activity.GetProperty("requestBodyPreview").GetString(), Is.EqualTo("q=a+b"));
Assert.That(activity.GetProperty("requestContentLength").GetInt32(), Is.EqualTo(5));
}

[Test]
Expand All @@ -37,6 +93,20 @@ await VerifyJson(json)
.ScrubMember("utc");
}

[Test]
public async Task InspectApiAfterNotFoundGeneratesRoute()
{
await using var host = await MockHttpTestHost.StartAsync();

await host.Client.GetAsync("/missing.txt");
var snapshot = await host.Client.GetFromJsonAsync<JsonElement>("/inspect/api");
var route = snapshot.GetProperty("activity")[0].GetProperty("route");

Assert.That(route.GetProperty("Request").GetProperty("Methods")[0].GetProperty("Method").GetString(), Is.EqualTo("GET"));
Assert.That(route.GetProperty("Request").GetProperty("AbsoluteUri").GetString(), Does.Contain("/missing.txt"));
Assert.That(route.GetProperty("Responses")[0].GetProperty("StatusCode").GetInt32(), Is.EqualTo(404));
}

[Test]
public async Task DisabledRouteGetDoesNotIncrementHits()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

<ItemGroup>
<EmbeddedResource Include="Inspect\inspect.html" />
<EmbeddedResource Include="../../icon/devlead.testing.mockhttp.png" LogicalName="Devlead.Testing.MockHttp.Tool.Inspect.favicon.png" />
<None Include="README.md" Pack="true" PackagePath="\" />
<None Include="../../icon/LICENSE.md" Pack="true" PackagePath="icon/LICENSE.md" />
<None Include="../../icon/devlead.testing.mockhttp.png" Pack="true" PackagePath="icon/devlead.testing.mockhttp.png" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ internal static partial class MockHttpWebHost
? informational.Split('+', 2)[0]
: typeof(MockHttpWebHost).Assembly.GetName().Version?.ToString() ?? string.Empty;

private static readonly byte[] FaviconPng = LoadEmbedded(
"Devlead.Testing.MockHttp.Tool.Inspect.favicon.png",
"The favicon resource is missing.");

private static IResult Favicon() => Results.File(FaviconPng, "image/png");

private static IResult InspectPage()
{
using var stream = typeof(MockHttpWebHost).Assembly
Expand All @@ -29,6 +35,15 @@ private static IResult InspectPage()
return Results.Content(reader.ReadToEnd(), "text/html", Encoding.UTF8);
}

private static byte[] LoadEmbedded(string name, string missing)
{
using var stream = typeof(MockHttpWebHost).Assembly.GetManifestResourceStream(name)
?? throw new InvalidOperationException(missing);
using var memory = new MemoryStream();
stream.CopyTo(memory);
return memory.ToArray();
}

private static IResult InspectState(RouteTable table, MockHttpListenUrl listen) =>
Results.Json(table.Snapshot(listen.Value, ApplicationVersion), JsonOptions);

Expand Down
4 changes: 3 additions & 1 deletion src/Devlead.Testing.MockHttp.Tool/Hosting/MockHttpWebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ public static WebApplicationBuilder CreateBuilder(RouteTable table, string liste
/// <param name="app">The web application.</param>
public static void Map(WebApplication app)
{
app.MapGet("/favicon.ico", Favicon);
app.MapGet("/inspect", InspectPage);
app.MapGet("/inspect/api", InspectState);
app.MapPost("/inspect/api/routes/toggle", InspectToggle);
app.MapDelete("/inspect/api/activity", InspectClearActivity);

var table = app.Services.GetRequiredService<RouteTable>();
app.UseWhen(
context => !context.Request.Path.StartsWithSegments("/inspect"),
context => !context.Request.Path.StartsWithSegments("/inspect")
&& !context.Request.Path.Equals("/favicon.ico", StringComparison.OrdinalIgnoreCase),
branch => branch.Run(table.InvokeAsync));
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json;

namespace Devlead.Testing.MockHttp.Tool.Inspect.Models;

internal sealed record ActivityEntry(
Expand All @@ -10,7 +12,11 @@ internal sealed record ActivityEntry(
string MatchedAbsoluteUri,
bool EnableDisable,
IReadOnlyDictionary<string, string[]> RequestHeaders,
string? RequestContentType,
int RequestContentLength,
string? RequestBodyPreview,
IReadOnlyDictionary<string, string[]> ResponseHeaders,
string? ContentType,
int ContentLength,
string? BodyPreview);
string? BodyPreview,
JsonElement Route);
Loading
Loading