namespace PS.IPAM.Tests.Mocks; using System.Net; /// /// A mock HTTP message handler for testing HTTP requests without making actual network calls. /// This handler does not dispose itself when the HttpClient is disposed, allowing reuse in tests. /// public class MockHttpMessageHandler : HttpMessageHandler { private readonly Queue _responses = new(); private readonly List _requests = new(); private bool _disposed = false; /// /// Gets all requests that were sent through this handler. /// public IReadOnlyList Requests => _requests.AsReadOnly(); /// /// Gets the last request that was sent through this handler. /// public HttpRequestMessage? LastRequest => _requests.LastOrDefault(); /// /// Queues a response to be returned for the next request. /// public MockHttpMessageHandler WithResponse(HttpStatusCode statusCode, string content, string contentType = "application/json") { _responses.Enqueue(new MockResponse(statusCode, content, contentType)); return this; } /// /// Queues a successful JSON response. /// public MockHttpMessageHandler WithJsonResponse(string jsonContent) { return WithResponse(HttpStatusCode.OK, jsonContent, "application/json"); } /// /// Queues a successful response with phpIPAM-style wrapper. /// public MockHttpMessageHandler WithSuccessResponse(string dataJson) { var response = $"{{\"code\":200,\"success\":true,\"data\":{dataJson}}}"; return WithJsonResponse(response); } /// /// Queues a 404 Not Found response. /// public MockHttpMessageHandler WithNotFoundResponse() { return WithResponse(HttpStatusCode.NotFound, "{\"code\":404,\"success\":false,\"message\":\"Not found\"}", "application/json"); } /// /// Queues an error response. /// public MockHttpMessageHandler WithErrorResponse(HttpStatusCode statusCode, string message) { var response = $"{{\"code\":{(int)statusCode},\"success\":false,\"message\":\"{message}\"}}"; return WithResponse(statusCode, response, "application/json"); } /// /// Queues an exception to be thrown on the next request. /// public MockHttpMessageHandler WithException(Exception exception) { _responses.Enqueue(new MockResponse(exception)); return this; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { if (_disposed) { throw new ObjectDisposedException(nameof(MockHttpMessageHandler)); } _requests.Add(request); if (_responses.Count == 0) { throw new InvalidOperationException("No mock response configured. Call WithResponse() before making requests."); } var mockResponse = _responses.Dequeue(); if (mockResponse.Exception != null) { throw mockResponse.Exception; } var response = new HttpResponseMessage(mockResponse.StatusCode) { Content = new StringContent(mockResponse.Content, System.Text.Encoding.UTF8, mockResponse.ContentType), RequestMessage = request }; return Task.FromResult(response); } protected override void Dispose(bool disposing) { // Don't actually dispose - allow reuse in tests // The test itself is responsible for cleanup } /// /// Actually disposes the handler. Call this in test cleanup. /// public void ForceDispose() { _disposed = true; base.Dispose(true); } /// /// Verifies that a request was made to the expected URL. /// public bool WasRequestMadeTo(string urlContains) { return _requests.Any(r => r.RequestUri?.ToString().Contains(urlContains) == true); } /// /// Verifies that a request with the expected method was made. /// public bool WasRequestMadeWithMethod(HttpMethod method) { return _requests.Any(r => r.Method == method); } /// /// Gets the value of a header from the last request. /// public string? GetLastRequestHeader(string headerName) { if (LastRequest?.Headers.TryGetValues(headerName, out var values) == true) { return values.FirstOrDefault(); } return null; } private class MockResponse { public HttpStatusCode StatusCode { get; } public string Content { get; } public string ContentType { get; } public Exception? Exception { get; } public MockResponse(HttpStatusCode statusCode, string content, string contentType) { StatusCode = statusCode; Content = content; ContentType = contentType; } public MockResponse(Exception exception) { Exception = exception; StatusCode = HttpStatusCode.InternalServerError; Content = string.Empty; ContentType = string.Empty; } } }