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
20 changes: 20 additions & 0 deletions src/Teapot.Web.Tests/IntegrationTests/StatusCodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,24 @@ public async Task ResponseNoContent([Values] TestCase testCase)
Assert.That(response.Content.Headers.ContentLength, Is.EqualTo(0));
});
}

[TestCaseSource(typeof(TestCases), nameof(TestCases.StatusCodesWithHeaders))]
public async Task ResponseWithHeaders([Values] TestCase testCase)
{
string uri = $"/{testCase.Code}";
using HttpRequestMessage httpRequest = new(httpMethod, uri);
using HttpResponseMessage response = await _httpClient.SendAsync(httpRequest);
Assert.That((int)response.StatusCode, Is.EqualTo(testCase.Code));
string body = await response.Content.ReadAsStringAsync();
Assert.Multiple(() =>
{
// Some headers as skipped as their info is represented elsewhere in the API, so we'll filter them out
List<string> headersToSkip = ["Content-Type", "Content-Range"];
foreach (KeyValuePair<string, string> header in testCase.TeapotStatusCodeMetadata.IncludeHeaders.Where(h => ! headersToSkip.Contains(h.Key)))
{
Assert.That(response.Headers.Contains(header.Key), Is.True);
Assert.That(response.Headers.GetValues(header.Key).FirstOrDefault(), Is.EqualTo(header.Value));
}
});
}
}
13 changes: 7 additions & 6 deletions src/Teapot.Web.Tests/Teapot.Web.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.4.0">
<PackageReference Include="NUnit" Version="4.3.2" />
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
5 changes: 5 additions & 0 deletions src/Teapot.Web.Tests/TestCases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public class TestCases
public static IEnumerable<TestCase> StatusCodesNoContent =>
NoContentStatusCodes.Select(Map);

public static IEnumerable<TestCase> StatusCodesWithHeaders =>
All
.Where(x => x.Value.IncludeHeaders.Count != 0)
.Select(Map);

private static TestCase Map(HttpStatusCode code)
{
int key = (int)code;
Expand Down
8 changes: 4 additions & 4 deletions src/Teapot.Web.Tests/UnitTests/SleepTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void Setup() {
public void SleepReadFromQuery()
{
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, sleep: Sleep), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new(), sleep: Sleep), null, request.Object);

Assert.Multiple(() =>
{
Expand All @@ -42,7 +42,7 @@ public void SleepReadFromHeader()
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
request.Object.Headers.Append(StatusExtensions.SLEEP_HEADER, Sleep.ToString());

IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new()), null, request.Object);

Assert.Multiple(() => {
Assert.That(result, Is.InstanceOf<CustomHttpStatusCodeResult>());
Expand All @@ -57,7 +57,7 @@ public void SleepReadFromQSTakesPriorityHeader() {
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
request.Object.Headers.Append(StatusExtensions.SLEEP_HEADER, Sleep.ToString());

IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, sleep:Sleep * 2), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new(), sleep: Sleep * 2), null, request.Object);

Assert.Multiple(() => {
Assert.That(result, Is.InstanceOf<CustomHttpStatusCodeResult>());
Expand All @@ -73,7 +73,7 @@ public void BadSleepHeaderIgnored()
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
request.Object.Headers.Append(StatusExtensions.SLEEP_HEADER, "invalid");

IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new()), null, request.Object);

Assert.Multiple(() =>
{
Expand Down
8 changes: 4 additions & 4 deletions src/Teapot.Web.Tests/UnitTests/SuppressBodyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void Setup()
public void SuppressBodyReadFromQuery(bool? suppressBody)
{
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, suppressBody:suppressBody), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new(), suppressBody: suppressBody), null, request.Object);

Assert.Multiple(() =>
{
Expand All @@ -48,7 +48,7 @@ public void SuppressBodyReadFromHeader(string? suppressBody)
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
request.Object.Headers.Append(StatusExtensions.SUPPRESS_BODY_HEADER, suppressBody);

IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new()), null, request.Object);

Assert.Multiple(() =>
{
Expand Down Expand Up @@ -76,7 +76,7 @@ public void SuppressBodyReadFromQSTakesPriorityHeader(string? headerValue, bool?
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
request.Object.Headers.Append(StatusExtensions.SUPPRESS_BODY_HEADER, headerValue);

IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, suppressBody: queryStringValue), null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new(), suppressBody: queryStringValue), null, request.Object);

Assert.Multiple(() =>
{
Expand All @@ -98,7 +98,7 @@ public void BadSuppressBodyHeaderIgnored()
Mock<HttpRequest> request = HttpRequestHelper.GenerateMockRequest();
request.Object.Headers.Append(StatusExtensions.SUPPRESS_BODY_HEADER, "invalid");

IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200),null, request.Object, _statusCodes);
IResult result = StatusExtensions.CommonHandleStatusRequestAsync(new ResponseOptions(200, new()), null, request.Object);

Assert.Multiple(() =>
{
Expand Down
12 changes: 6 additions & 6 deletions src/Teapot.Web/Models/TeapotStatusCodeMetadataCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public TeapotStatusCodeMetadataCollection(
Description = "Moved Permanently",
IncludeHeaders = new Dictionary<string, string>
{
{"Location", "https://httpstat.us"},
{"Location", "https://httpstat.us/"},
{"Retry-After", "5"}
}
});
Expand All @@ -124,15 +124,15 @@ public TeapotStatusCodeMetadataCollection(
Description = "Found",
IncludeHeaders = new Dictionary<string, string>
{
{"Location", "https://httpstat.us"}
{"Location", "https://httpstat.us/"}
}
});
Add(303, new TeapotStatusCodeMetadata
{
Description = "See Other",
IncludeHeaders = new Dictionary<string, string>
{
{"Location", "https://httpstat.us"}
{"Location", "https://httpstat.us/"}
}
});
Add(304, new TeapotStatusCodeMetadata
Expand All @@ -145,7 +145,7 @@ public TeapotStatusCodeMetadataCollection(
Description = "Use Proxy",
IncludeHeaders = new Dictionary<string, string>
{
{"Location", "https://httpstat.us"}
{"Location", "https://httpstat.us/"}
}
});
Add(306, new TeapotStatusCodeMetadata
Expand All @@ -157,15 +157,15 @@ public TeapotStatusCodeMetadataCollection(
Description = "Temporary Redirect",
IncludeHeaders = new Dictionary<string, string>
{
{"Location", "https://httpstat.us"}
{"Location", "https://httpstat.us/"}
}
});
Add(308, new TeapotStatusCodeMetadata
{
Description = "Permanent Redirect",
IncludeHeaders = new Dictionary<string, string>
{
{"Location", "https://httpstat.us"}
{"Location", "https://httpstat.us/"}
}
});

Expand Down
6 changes: 3 additions & 3 deletions src/Teapot.Web/ResponseOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ namespace Teapot.Web;
public record class ResponseOptions
{
public ResponseOptions(int statusCode,
TeapotStatusCodeMetadata metadata,
int? sleep = null,
int? sleepAfterHeaders = null,
bool? abortBeforeHeaders = null,
bool? abortAfterHeaders = null,
bool? abortDuringBody = null,
bool? suppressBody = null,
bool? dribbleBody = null,
TeapotStatusCodeMetadata? metadata = null,
Dictionary<string, StringValues>? customHeaders = null)
{
StatusCode = statusCode;
Expand All @@ -25,8 +25,8 @@ public ResponseOptions(int statusCode,
AbortDuringBody = abortDuringBody;
SuppressBody = suppressBody;
DribbleBody = dribbleBody;
CustomHeaders = customHeaders ?? new Dictionary<string, StringValues>();
Metadata = metadata ?? new();
CustomHeaders = customHeaders ?? [];
Metadata = metadata;
}

public int StatusCode { get; set; }
Expand Down
27 changes: 15 additions & 12 deletions src/Teapot.Web/StatusExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,23 @@ internal static IResult HandleStatusRequestAsync(
[FromServices] TeapotStatusCodeMetadataCollection statusCodes
)
{
ResponseOptions options = new(status)
TeapotStatusCodeMetadata statusData = statusCodes.TryGetValue(status, out TeapotStatusCodeMetadata? value) ?
value :
new TeapotStatusCodeMetadata { Description = $"{status} Unknown Code" };

ResponseOptions options = new(status, statusData)
{
Sleep = sleep,
SuppressBody = suppressBody
};
return CommonHandleStatusRequestAsync(options, wildcard, req, statusCodes);
return CommonHandleStatusRequestAsync(options, wildcard, req);
}

internal static IResult CommonHandleStatusRequestAsync(
ResponseOptions options,
string? wildcard,
HttpRequest req,
TeapotStatusCodeMetadataCollection statusCodes
)
HttpRequest req)
{
TeapotStatusCodeMetadata statusData = statusCodes.TryGetValue(options.StatusCode, out TeapotStatusCodeMetadata? value) ?
value :
new TeapotStatusCodeMetadata { Description = $"{options.StatusCode} Unknown Code" };
options.Sleep ??= ParseHeaderInt(req, SLEEP_HEADER);
options.SleepAfterHeaders ??= ParseHeaderInt(req, SLEEP_AFTER_HEADERS);
options.SuppressBody ??= ParseHeaderBool(req, SUPPRESS_BODY_HEADER);
Expand All @@ -78,8 +77,7 @@ TeapotStatusCodeMetadataCollection statusCodes
options.AbortBeforeHeaders ??= ParseHeaderBool(req, ABORT_BEFORE_HEADERS);
options.AbortDuringBody ??= ParseHeaderBool(req, ABORT_DURING_BODY);


Dictionary<string, StringValues> customResponseHeaders = req.Headers
options.CustomHeaders = req.Headers
.Where(header => header.Key.StartsWith(CUSTOM_RESPONSE_HEADER_PREFIX, StringComparison.InvariantCultureIgnoreCase))
.ToDictionary(
header => header.Key.Replace(CUSTOM_RESPONSE_HEADER_PREFIX, string.Empty, StringComparison.InvariantCultureIgnoreCase),
Expand All @@ -98,8 +96,13 @@ internal static IResult HandleRandomRequest(
{
try
{
var options = new ResponseOptions(GetRandomStatus(range));
return CommonHandleStatusRequestAsync(options, wildcard, req, statusCodes);
var status = GetRandomStatus(range);
TeapotStatusCodeMetadata statusData = statusCodes.TryGetValue(status, out TeapotStatusCodeMetadata? value) ?
value :
new TeapotStatusCodeMetadata { Description = $"{status} Unknown Code" };

ResponseOptions options = new(status, statusData);
return CommonHandleStatusRequestAsync(options, wildcard, req);
}
catch
{
Expand Down