q2ebanking / boa-constrictor

Boa Constrictor is a C# implementation of the Screenplay Pattern. Its primary use case is Web UI and REST API test automation. Boa Constrictor helps you make better interactions for better automation!
https://q2ebanking.github.io/boa-constrictor/
Other
118 stars 40 forks source link

[FEATURE]: Improvements to RestSharp dumping #264

Closed thePantz closed 1 month ago

thePantz commented 1 year ago

Description

Since we've updated to the latest RestSharp there may be new ways to approach dumping from.

I came across this comment and got inspired. You could in theory, write a message handler that dumps the raw HttpRequestMessage rather than the RestSharp object. The nice part about this is HttpRequestMessage already has a ToString() overload. So there isn't much code needed to get a nicely formatted dump.

At first glance, it's pretty easy to get working.

  1. Write a DelagatingHandler that takes in Boas ILogger

    public class LoggingDelegatingHandler : DelegatingHandler
    {
    public ILogger logger;
    
    public LoggingDelegatingHandler(ILogger logger, HttpMessageHandler innerHandler) : base(innerHandler)
    {
        this.logger = logger;
    }
    protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        logger.Log(request.ToString());
        return base.Send(request, cancellationToken);
    }
    
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        logger.Log(request.ToString());
        return base.SendAsync(request, cancellationToken);
    }
    }
  2. Configure your RestClient to use the custom message handler

    private RestClient client;

    [SetUp]
    public void Initialize()
    {
        var options = new RestClientOptions
        {
            BaseUrl = new Uri( "https://dog.ceo/"),
            ConfigureMessageHandler = handler => new LoggingDelegatingHandler(new ConsoleLogger(), handler)
        };

        this.client = new RestClient(options);
    }
  1. Make your request
    [Test]
    public void DumpTest()
    {
        var request = new RestRequest("api/breeds/image/random");
        this.client.Get(request);
    }

Since I configured this to use Boas ConsoleLogger the following shows up in the TestOutput window. But of course you could write a logger/Delegating Handler to log to file if you prefer 👍

2023-05-25 22:04:22Z [INFO] Method: GET, RequestUri: 'https://dog.ceo/api/breeds/image/random', Version: 1.1, Content: <null>, Headers:
{
  Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
  User-Agent: RestSharp/110.2.0.0
}

All this to say, I believe we could replace most of the current RequestDumping feature with a custom message handler.

Alternatives

Work with what we got. Refactor the serialization objects to comply with the modern RestSharp objects.

Anything else?

I don't use the dumping feature myself that often so I could be missing something. Could require a different implementation depending on what version of .NET you're on... Still prefer this over versions of RestSharp.

Commitments

thePantz commented 1 year ago

I should mention, the same is true of HttpResponseMessage

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
    logger.Log(request.ToString());
    var response = base.SendAsync(request, cancellationToken);
    logger.Log(response.Result.ToString());
    logger.Log(response.Result.Content.ReadAsStringAsync().Result);

    return response;
}

Output:

2023-05-25 22:54:46Z [INFO] Method: GET, RequestUri: 'https://dog.ceo/api/breeds/image/random', Version: 1.1, Content: <null>, Headers:
{
  Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
  User-Agent: RestSharp/110.2.0.0
}
2023-05-25 22:54:47Z [INFO] StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.DecompressionHandler+BrotliDecompressedContent, Headers:
{
  Date: Thu, 25 May 2023 22:54:46 GMT
  Transfer-Encoding: chunked
  Connection: keep-alive
  X-Powered-By: PHP/8.1.0
  Cache-Control: no-cache, private
  Access-Control-Allow-Origin: *
  Via: 1.1 varnish (Varnish/6.3), 1.1 varnish (Varnish/6.3)
  X-Cache-Hits: 0
  X-Cache: MISS
  Age: 0
  Vary: Accept-Encoding
  CF-Cache-Status: DYNAMIC
  Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=dhFiIAr2R4ExS89VECfzOiXc7iHfOWpm6SAhsDLw0fKWNZ3IqaIubA7pLRvKIxi08I%2FGbBOhLoKZJ8xkQrjd9udU0tZzL%2F7xarYx23fxrg7GzEP2M85qNgCI"}],"group":"cf-nel","max_age":604800}
  NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
  Server: cloudflare
  CF-RAY: 7cd15cb16e17f4ae-YVR
  Alt-Svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
  Content-Type: application/json
}
2023-05-25 22:54:47Z [INFO] {"message":"https:\/\/images.dog.ceo\/breeds\/basenji\/n02110806_2926.jpg","status":"success"}

Obviously not the best implementation but as a quick PoC the info we need is accessible