diff --git a/src/Teapot.Web.Tests/IntegrationTests/StatusCodeTests.cs b/src/Teapot.Web.Tests/IntegrationTests/StatusCodeTests.cs index e7f8ae2..301620c 100644 --- a/src/Teapot.Web.Tests/IntegrationTests/StatusCodeTests.cs +++ b/src/Teapot.Web.Tests/IntegrationTests/StatusCodeTests.cs @@ -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 headersToSkip = ["Content-Type", "Content-Range"]; + foreach (KeyValuePair 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)); + } + }); + } } diff --git a/src/Teapot.Web.Tests/Teapot.Web.Tests.csproj b/src/Teapot.Web.Tests/Teapot.Web.Tests.csproj index 856557b..19c2858 100644 --- a/src/Teapot.Web.Tests/Teapot.Web.Tests.csproj +++ b/src/Teapot.Web.Tests/Teapot.Web.Tests.csproj @@ -5,19 +5,20 @@ enable enable false + true - - + + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Teapot.Web.Tests/TestCases.cs b/src/Teapot.Web.Tests/TestCases.cs index dbe51b5..cf7cedc 100644 --- a/src/Teapot.Web.Tests/TestCases.cs +++ b/src/Teapot.Web.Tests/TestCases.cs @@ -32,6 +32,11 @@ public class TestCases public static IEnumerable StatusCodesNoContent => NoContentStatusCodes.Select(Map); + public static IEnumerable StatusCodesWithHeaders => + All + .Where(x => x.Value.IncludeHeaders.Count != 0) + .Select(Map); + private static TestCase Map(HttpStatusCode code) { int key = (int)code; diff --git a/src/Teapot.Web.Tests/UnitTests/SleepTests.cs b/src/Teapot.Web.Tests/UnitTests/SleepTests.cs index d61a527..98c4b62 100644 --- a/src/Teapot.Web.Tests/UnitTests/SleepTests.cs +++ b/src/Teapot.Web.Tests/UnitTests/SleepTests.cs @@ -25,7 +25,7 @@ public void Setup() { public void SleepReadFromQuery() { Mock 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(() => { @@ -42,7 +42,7 @@ public void SleepReadFromHeader() Mock 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()); @@ -57,7 +57,7 @@ public void SleepReadFromQSTakesPriorityHeader() { Mock 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()); @@ -73,7 +73,7 @@ public void BadSleepHeaderIgnored() Mock 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(() => { diff --git a/src/Teapot.Web.Tests/UnitTests/SuppressBodyTests.cs b/src/Teapot.Web.Tests/UnitTests/SuppressBodyTests.cs index 065e53d..fb6e504 100644 --- a/src/Teapot.Web.Tests/UnitTests/SuppressBodyTests.cs +++ b/src/Teapot.Web.Tests/UnitTests/SuppressBodyTests.cs @@ -28,7 +28,7 @@ public void Setup() public void SuppressBodyReadFromQuery(bool? suppressBody) { Mock 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(() => { @@ -48,7 +48,7 @@ public void SuppressBodyReadFromHeader(string? suppressBody) Mock 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(() => { @@ -76,7 +76,7 @@ public void SuppressBodyReadFromQSTakesPriorityHeader(string? headerValue, bool? Mock 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(() => { @@ -98,7 +98,7 @@ public void BadSuppressBodyHeaderIgnored() Mock 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(() => { diff --git a/src/Teapot.Web/Models/TeapotStatusCodeMetadataCollection.cs b/src/Teapot.Web/Models/TeapotStatusCodeMetadataCollection.cs index aaeb8a2..e14e996 100644 --- a/src/Teapot.Web/Models/TeapotStatusCodeMetadataCollection.cs +++ b/src/Teapot.Web/Models/TeapotStatusCodeMetadataCollection.cs @@ -115,7 +115,7 @@ public TeapotStatusCodeMetadataCollection( Description = "Moved Permanently", IncludeHeaders = new Dictionary { - {"Location", "https://httpstat.us"}, + {"Location", "https://httpstat.us/"}, {"Retry-After", "5"} } }); @@ -124,7 +124,7 @@ public TeapotStatusCodeMetadataCollection( Description = "Found", IncludeHeaders = new Dictionary { - {"Location", "https://httpstat.us"} + {"Location", "https://httpstat.us/"} } }); Add(303, new TeapotStatusCodeMetadata @@ -132,7 +132,7 @@ public TeapotStatusCodeMetadataCollection( Description = "See Other", IncludeHeaders = new Dictionary { - {"Location", "https://httpstat.us"} + {"Location", "https://httpstat.us/"} } }); Add(304, new TeapotStatusCodeMetadata @@ -145,7 +145,7 @@ public TeapotStatusCodeMetadataCollection( Description = "Use Proxy", IncludeHeaders = new Dictionary { - {"Location", "https://httpstat.us"} + {"Location", "https://httpstat.us/"} } }); Add(306, new TeapotStatusCodeMetadata @@ -157,7 +157,7 @@ public TeapotStatusCodeMetadataCollection( Description = "Temporary Redirect", IncludeHeaders = new Dictionary { - {"Location", "https://httpstat.us"} + {"Location", "https://httpstat.us/"} } }); Add(308, new TeapotStatusCodeMetadata @@ -165,7 +165,7 @@ public TeapotStatusCodeMetadataCollection( Description = "Permanent Redirect", IncludeHeaders = new Dictionary { - {"Location", "https://httpstat.us"} + {"Location", "https://httpstat.us/"} } }); diff --git a/src/Teapot.Web/ResponseOptions.cs b/src/Teapot.Web/ResponseOptions.cs index 5ca3102..9742665 100644 --- a/src/Teapot.Web/ResponseOptions.cs +++ b/src/Teapot.Web/ResponseOptions.cs @@ -7,6 +7,7 @@ namespace Teapot.Web; public record class ResponseOptions { public ResponseOptions(int statusCode, + TeapotStatusCodeMetadata metadata, int? sleep = null, int? sleepAfterHeaders = null, bool? abortBeforeHeaders = null, @@ -14,7 +15,6 @@ public ResponseOptions(int statusCode, bool? abortDuringBody = null, bool? suppressBody = null, bool? dribbleBody = null, - TeapotStatusCodeMetadata? metadata = null, Dictionary? customHeaders = null) { StatusCode = statusCode; @@ -25,8 +25,8 @@ public ResponseOptions(int statusCode, AbortDuringBody = abortDuringBody; SuppressBody = suppressBody; DribbleBody = dribbleBody; - CustomHeaders = customHeaders ?? new Dictionary(); - Metadata = metadata ?? new(); + CustomHeaders = customHeaders ?? []; + Metadata = metadata; } public int StatusCode { get; set; } diff --git a/src/Teapot.Web/StatusExtensions.cs b/src/Teapot.Web/StatusExtensions.cs index 2169e98..04139f5 100644 --- a/src/Teapot.Web/StatusExtensions.cs +++ b/src/Teapot.Web/StatusExtensions.cs @@ -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); @@ -78,8 +77,7 @@ TeapotStatusCodeMetadataCollection statusCodes options.AbortBeforeHeaders ??= ParseHeaderBool(req, ABORT_BEFORE_HEADERS); options.AbortDuringBody ??= ParseHeaderBool(req, ABORT_DURING_BODY); - - Dictionary 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), @@ -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 {