Skip to content
Open
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 @@ -526,7 +526,8 @@ private static void PopulateHeaders(HttpHeaders headers, IEnumerable<KeyValuePai

private static async Task<HttpResponseMessage> BuildResponseAsync(HttpRequestMessage request, HttpInterceptionResponse response)
{
var result = new HttpResponseMessage(response.StatusCode);
var statusCode = response.StatusCodeFactory?.Invoke() ?? response.StatusCode;
var result = new HttpResponseMessage(statusCode);

try
{
Expand Down
2 changes: 2 additions & 0 deletions src/HttpClientInterception/HttpInterceptionResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ internal sealed class HttpInterceptionResponse

internal Func<Task<byte[]>>? ContentFactory { get; set; }

internal Func<HttpStatusCode>? StatusCodeFactory { get; set; }

internal Func<Task<Stream>>? ContentStream { get; set; }

internal string? ContentMediaType { get; set; }
Expand Down
29 changes: 29 additions & 0 deletions src/HttpClientInterception/HttpRequestInterceptionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class HttpRequestInterceptionBuilder

private HttpStatusCode _statusCode = HttpStatusCode.OK;

private Func<HttpStatusCode>? _statusCodeFactory;

private UriBuilder _uriBuilder = new();

private Version? _version;
Expand Down Expand Up @@ -733,7 +735,34 @@ public HttpRequestInterceptionBuilder WithStatus(int statusCode)
public HttpRequestInterceptionBuilder WithStatus(HttpStatusCode statusCode)
{
_statusCode = statusCode;
_statusCodeFactory = null;
IncrementRevision();
return this;
}

/// <summary>
/// Sets the function to use to build the response status code.
/// </summary>
/// <param name="statusCodeFactory">A delegate to a method that returns the HTTP status code.</param>
/// <returns>
/// The current <see cref="HttpRequestInterceptionBuilder"/>.
/// </returns>
/// <remarks>
/// Pass a value of <see langword="null"/> to reset to no status code factory.
/// </remarks>
public HttpRequestInterceptionBuilder WithStatus(Func<HttpStatusCode>? statusCodeFactory)
{
if (statusCodeFactory is null)
{
_statusCodeFactory = null;
}
else
{
_statusCodeFactory = statusCodeFactory;
}

IncrementRevision();

return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2585,6 +2585,259 @@ public static async Task Can_Do_Custom_Matching_Even_Number_Requests_Return_200_
fourthResponse.StatusCode.ShouldBe(HttpStatusCode.TooManyRequests);
}

[Fact]
public static async Task WithStatus_Factory_Returns_Dynamic_Status_Code()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Dynamic content";
var callCount = 0;

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(() =>
{
callCount++;
return callCount == 1 ? HttpStatusCode.OK : HttpStatusCode.NotFound;
})
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response1 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);
var response2 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.NotFound);

// Assert
response1.ShouldBe(expected);
response2.ShouldBe(expected);
callCount.ShouldBe(2);
}

[Fact]
public static async Task WithStatus_Factory_With_Null_Resets_To_Static_Status_Code()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(HttpStatusCode.BadRequest)
.WithStatus(null) // Reset to static
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.BadRequest);

// Assert
response.ShouldBe(expected);
}

[Fact]
public static async Task WithStatus_Factory_Overrides_Static_Status_Code()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(HttpStatusCode.BadRequest) // Static status code
.WithStatus(() => HttpStatusCode.OK) // Factory overrides static
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);

// Assert
response.ShouldBe(expected);
}

[Fact]
public static async Task WithStatus_Static_Overrides_Factory_Status_Code()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(() => HttpStatusCode.OK) // Factory status code
.WithStatus(HttpStatusCode.BadRequest) // Static overrides factory
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.BadRequest);

// Assert
response.ShouldBe(expected);
}

[Fact]
public static async Task WithStatus_Factory_With_Conditional_Logic()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";
var isFirstRequest = true;

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(() =>
{
if (isFirstRequest)
{
isFirstRequest = false;
return HttpStatusCode.OK;
}

return HttpStatusCode.TooManyRequests;
})
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response1 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);
var response2 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.TooManyRequests);

// Assert
response1.ShouldBe(expected);
response2.ShouldBe(expected);
}

[Fact]
public static async Task WithStatus_Factory_With_Exception_Handling()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";
var shouldThrow = true;

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(() =>
{
if (shouldThrow)
{
shouldThrow = false;
throw new InvalidOperationException("Test exception");
}

return HttpStatusCode.OK;
})
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act & Assert
await Should.ThrowAsync<InvalidOperationException>(async () =>
await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK));
}

[Fact]
public static async Task WithStatus_Factory_With_Multiple_Registrations()
{
// Arrange
var requestUri1 = "https://test.local/post";
var requestUri2 = "https://bing.com/";
var expected = "Test content";

var builder1 = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri1)
.WithStatus(() => HttpStatusCode.OK)
.WithContent(expected);

var builder2 = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri2)
.WithStatus(() => HttpStatusCode.BadRequest)
.WithContent(expected);

var options = new HttpClientInterceptorOptions()
.Register(builder1)
.Register(builder2);

// Act
var response1 = await HttpAssert.GetAsync(options, requestUri1, HttpStatusCode.OK);
var response2 = await HttpAssert.GetAsync(options, requestUri2, HttpStatusCode.BadRequest);

// Assert
response1.ShouldBe(expected);
response2.ShouldBe(expected);
}

[Fact]
public static async Task WithStatus_Factory_With_Interception_Callback()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";
var callbackCount = 0;
var statusCodeCount = 0;

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(() =>
{
statusCodeCount++;
return HttpStatusCode.OK;
})
.WithInterceptionCallback((request) =>
{
callbackCount++;
return Task.FromResult(true);
})
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);

// Assert
response.ShouldBe(expected);
callbackCount.ShouldBe(1);
statusCodeCount.ShouldBe(1);
}

[Fact]
public static async Task WithStatus_Factory_Called_For_Each_Request()
{
// Arrange
var requestUri = "https://test.local/post";
var expected = "Test content";
var factoryCallCount = 0;

var builder = new HttpRequestInterceptionBuilder()
.ForUrl(requestUri)
.WithStatus(() =>
{
factoryCallCount++;
return HttpStatusCode.OK;
})
.WithContent(expected);

var options = new HttpClientInterceptorOptions().Register(builder);

// Act
var response1 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);
var response2 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);
var response3 = await HttpAssert.GetAsync(options, requestUri, HttpStatusCode.OK);

// Assert
response1.ShouldBe(expected);
response2.ShouldBe(expected);
response3.ShouldBe(expected);
factoryCallCount.ShouldBe(3); // Factory should be called for each request
}

private sealed class CustomObject
{
internal enum Color
Expand Down