Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support not recording request body #2692

Merged
5 commits merged into from
Feb 8, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public async void TestSetMatcherIndividualRecording()
{
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
var httpContext = new DefaultHttpContext();
await testRecordingHandler.StartPlayback("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
await testRecordingHandler.StartPlaybackAsync("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
var recordingId = httpContext.Response.Headers["x-recording-id"];
httpContext.Request.Headers["x-recording-id"] = recordingId;
httpContext.Request.Headers["x-abstraction-identifier"] = "BodilessMatcher";
Expand Down Expand Up @@ -382,7 +382,7 @@ public async void TestAddSanitizerIndividualRecording()
{
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
var httpContext = new DefaultHttpContext();
await testRecordingHandler.StartPlayback("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
await testRecordingHandler.StartPlaybackAsync("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
var recordingId = httpContext.Response.Headers["x-recording-id"];
httpContext.Request.Headers["x-recording-id"] = recordingId;
httpContext.Request.Headers["x-abstraction-identifier"] = "HeaderRegexSanitizer";
Expand Down Expand Up @@ -569,7 +569,7 @@ public async void TestAddTransformIndividualRecording()
{
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
var httpContext = new DefaultHttpContext();
await testRecordingHandler.StartPlayback("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
await testRecordingHandler.StartPlaybackAsync("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
var recordingId = httpContext.Response.Headers["x-recording-id"];
var apiVersion = "2016-03-21";
httpContext.Request.Headers["x-api-version"] = apiVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public void RecordMatcherThrowsExceptionsWithDetails()
" <Some-Other-Header> is absent in record, value <V>" + Environment.NewLine +
" <Extra-Header> is absent in request, value <Extra-Value>" + Environment.NewLine +
"Body differences:" + Environment.NewLine +
"Request and response bodies do not match at index 40:" + Environment.NewLine +
"Request and record bodies do not match at index 40:" + Environment.NewLine +
" request: \"e and long.\"" + Environment.NewLine +
" record: \"e and long but it also doesn't\"" + Environment.NewLine,
exception.Message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using Xunit;

namespace Azure.Sdk.Tools.TestProxy.Tests
Expand Down Expand Up @@ -190,7 +196,7 @@ public async Task TestInMemoryPurgesSucessfully()
var httpContext = new DefaultHttpContext();
var key = recordingHandler.InMemorySessions.Keys.First();

await recordingHandler.StartPlayback(key, httpContext.Response, Common.RecordingType.InMemory);
await recordingHandler.StartPlaybackAsync(key, httpContext.Response, Common.RecordingType.InMemory);
var playbackSession = httpContext.Response.Headers["x-recording-id"];
recordingHandler.StopPlayback(playbackSession, true);

Expand All @@ -204,7 +210,7 @@ public async Task TestInMemoryDoesntPurgeErroneously()
var httpContext = new DefaultHttpContext();
var key = recordingHandler.InMemorySessions.Keys.First();

await recordingHandler.StartPlayback(key, httpContext.Response, Common.RecordingType.InMemory);
await recordingHandler.StartPlaybackAsync(key, httpContext.Response, Common.RecordingType.InMemory);
var playbackSession = httpContext.Response.Headers["x-recording-id"];
recordingHandler.StopPlayback(playbackSession, false);

Expand All @@ -221,7 +227,7 @@ public async Task TestLoadOfAbsoluteRecording()

var recordingHandler = new RecordingHandler(tmpPath);

await recordingHandler.StartPlayback(pathToRecording, httpContext.Response);
await recordingHandler.StartPlaybackAsync(pathToRecording, httpContext.Response);

var playbackSession = recordingHandler.PlaybackSessions.First();
var entry = playbackSession.Value.Session.Entries.First();
Expand All @@ -237,7 +243,7 @@ public async Task TestLoadOfRelativeRecording()
var pathToRecording = "Test.RecordEntries/oauth_request.json";
var recordingHandler = new RecordingHandler(currentPath);

await recordingHandler.StartPlayback(pathToRecording, httpContext.Response);
await recordingHandler.StartPlaybackAsync(pathToRecording, httpContext.Response);

var playbackSession = recordingHandler.PlaybackSessions.First();
var entry = playbackSession.Value.Session.Entries.First();
Expand Down Expand Up @@ -292,6 +298,115 @@ public void TestWriteRelativeRecording()
}
}

[Fact]
public async Task TestCanSkipRecordingRequestBody()
{
var currentPath = Directory.GetCurrentDirectory();
var httpContext = new DefaultHttpContext();
var pathToRecording = "recordings/skip_body";
var recordingHandler = new RecordingHandler(currentPath);
var fullPathToRecording = Path.Combine(currentPath, pathToRecording) + ".json";

recordingHandler.StartRecording(pathToRecording, httpContext.Response);
var sessionId = httpContext.Response.Headers["x-recording-id"].ToString();

CreateRecordModeRequest(httpContext, "request-body");

var mockClient = new HttpClient(new MockHttpHandler());
await recordingHandler.HandleRecordRequestAsync(sessionId, httpContext.Request, httpContext.Response, mockClient);
recordingHandler.StopRecording(sessionId);

try
{
using var fileStream = File.Open(fullPathToRecording, FileMode.Open);
using var doc = JsonDocument.Parse(fileStream);
var record = RecordSession.Deserialize(doc.RootElement);
var entry = record.Entries.First();
Assert.Null(entry.Request.Body);
Assert.Equal(MockHttpHandler.DefaultResponse, Encoding.UTF8.GetString(entry.Response.Body));
}
finally
{
File.Delete(fullPathToRecording);
}
}

[Fact]
public async Task TestCanSkipRecordingEntireRequestResponse()
{
var currentPath = Directory.GetCurrentDirectory();
var httpContext = new DefaultHttpContext();
var pathToRecording = "recordings/skip_entry";
var recordingHandler = new RecordingHandler(currentPath);
var fullPathToRecording = Path.Combine(currentPath, pathToRecording) + ".json";

recordingHandler.StartRecording(pathToRecording, httpContext.Response);
var sessionId = httpContext.Response.Headers["x-recording-id"].ToString();

CreateRecordModeRequest(httpContext, "request-response");

var mockClient = new HttpClient(new MockHttpHandler());
await recordingHandler.HandleRecordRequestAsync(sessionId, httpContext.Request, httpContext.Response, mockClient);

httpContext = new DefaultHttpContext();
// send a second request that SHOULD be recorded
CreateRecordModeRequest(httpContext);
httpContext.Request.Headers.Remove("x-recording-skip");
httpContext.Request.Body = TestHelpers.GenerateStreamRequestBody("{ \"key\": \"value\" }");
await recordingHandler.HandleRecordRequestAsync(sessionId, httpContext.Request, httpContext.Response, mockClient);

recordingHandler.StopRecording(sessionId);

try
{
using var fileStream = File.Open(fullPathToRecording, FileMode.Open);
using var doc = JsonDocument.Parse(fileStream);
var record = RecordSession.Deserialize(doc.RootElement);
Assert.Single(record.Entries);
var entry = record.Entries.First();
Assert.Equal("value", JsonDocument.Parse(entry.Request.Body).RootElement.GetProperty("key").GetString());
Assert.Equal(MockHttpHandler.DefaultResponse, Encoding.UTF8.GetString(entry.Response.Body));
}
finally
{
File.Delete(fullPathToRecording);
}
}

[Theory]
[InlineData("invalid value")]
[InlineData("")]
[InlineData("request-body", "request-response")]
public async Task TestInvalidRecordModeThrows(params string[] values)
{
var currentPath = Directory.GetCurrentDirectory();
var httpContext = new DefaultHttpContext();
var pathToRecording = "recordings/invalid_record_mode";
var recordingHandler = new RecordingHandler(currentPath);

recordingHandler.StartRecording(pathToRecording, httpContext.Response);
var sessionId = httpContext.Response.Headers["x-recording-id"].ToString();

CreateRecordModeRequest(httpContext, new StringValues(values));

var mockClient = new HttpClient(new MockHttpHandler());
HttpException resultingException = await Assert.ThrowsAsync<HttpException>(
async () => await recordingHandler.HandleRecordRequestAsync(sessionId, httpContext.Request, httpContext.Response, mockClient)
);
Assert.Equal(HttpStatusCode.BadRequest, resultingException.StatusCode);
}

private static void CreateRecordModeRequest(DefaultHttpContext context, StringValues mode = default)
{
context.Request.Headers["x-recording-skip"] = mode;
context.Request.Headers["x-recording-upstream-base-uri"] = "https://contoso.net";
context.Request.ContentType = "application/json";
context.Request.Method = "PUT";
context.Request.Body = TestHelpers.GenerateStreamRequestBody("{ \"key\": \"value\" }");
// content length must be set for the body to be parsed in SetMatcher
context.Request.ContentLength = context.Request.Body.Length;
}

[Fact]
public async Task TestLoadNonexistentAbsoluteRecording()
{
Expand All @@ -304,7 +419,7 @@ public async Task TestLoadNonexistentAbsoluteRecording()
var recordingHandler = new RecordingHandler(tmpPath);

var resultingException = await Assert.ThrowsAsync<TestRecordingMismatchException>(
async () => await recordingHandler.StartPlayback(pathToRecording, httpContext.Response)
async () => await recordingHandler.StartPlaybackAsync(pathToRecording, httpContext.Response)
);
Assert.Contains($"{recordingPath} does not exist", resultingException.Message);
}
Expand All @@ -319,7 +434,7 @@ public async Task TestLoadNonexistentRelativeRecording()
var recordingHandler = new RecordingHandler(currentPath);

var resultingException = await Assert.ThrowsAsync<TestRecordingMismatchException>(
async () => await recordingHandler.StartPlayback(pathToRecording, httpContext.Response)
async () => await recordingHandler.StartPlaybackAsync(pathToRecording, httpContext.Response)
);
Assert.Contains($"{pathToRecording} does not exist", resultingException.Message);
}
Expand Down Expand Up @@ -392,7 +507,7 @@ public async Task TestStartPlaybackWithVariables()
var recordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
httpContext.Response.Body = new MemoryStream();

await recordingHandler.StartPlayback("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);
await recordingHandler.StartPlaybackAsync("Test.RecordEntries/oauth_request_with_variables.json", httpContext.Response);

Dictionary<string, string> results = JsonConvert.DeserializeObject<Dictionary<string, string>>(
TestHelpers.GenerateStringFromStream(httpContext.Response.Body)
Expand All @@ -409,7 +524,7 @@ public async Task TestStartPlaybackWithoutVariables()
var startHttpContext = new DefaultHttpContext();
var recordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());

await recordingHandler.StartPlayback("Test.RecordEntries/oauth_request.json", startHttpContext.Response);
await recordingHandler.StartPlaybackAsync("Test.RecordEntries/oauth_request.json", startHttpContext.Response);
}

[Fact]
Expand All @@ -425,4 +540,20 @@ public async Task CreateEntryUsesAbsoluteUri()
Assert.Equal(uri.AbsoluteUri, entry.RequestUri);
}
}

internal class MockHttpHandler : HttpMessageHandler
{
public const string DefaultResponse = "default response";

public MockHttpHandler()
{
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(DefaultResponse)
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Azure.Sdk.Tools.TestProxy.Common
{
public enum EntryRecordModel
public enum EntryRecordMode
{
Record,
DontRecord,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,49 +129,49 @@ public virtual RecordEntry FindMatch(RecordEntry request, IList<RecordEntry> ent
throw new TestRecordingMismatchException(GenerateException(request, bestScoreEntry));
}

public virtual int CompareBodies(byte[] requestBody, byte[] responseBody, StringBuilder descriptionBuilder = null)
public virtual int CompareBodies(byte[] requestBody, byte[] recordBody, StringBuilder descriptionBuilder = null)
{
if (!_compareBodies)
{
return 0;
}

if (requestBody == null && responseBody == null)
if (requestBody == null && recordBody == null)
{
return 0;
}

if (requestBody == null)
{
descriptionBuilder?.AppendLine("Request has body but response doesn't");
descriptionBuilder?.AppendLine("Record has body but request doesn't");
return 1;
}

if (responseBody == null)
if (recordBody == null)
{
descriptionBuilder?.AppendLine("Response has body but request doesn't");
descriptionBuilder?.AppendLine("Request has body but record doesn't");
return 1;
}

if (!requestBody.SequenceEqual(responseBody))
if (!requestBody.SequenceEqual(recordBody))
{
if (descriptionBuilder != null)
{
var minLength = Math.Min(requestBody.Length, responseBody.Length);
var minLength = Math.Min(requestBody.Length, recordBody.Length);
int i;
for (i = 0; i < minLength - 1; i++)
{
if (requestBody[i] != responseBody[i])
if (requestBody[i] != recordBody[i])
{
break;
}
}
descriptionBuilder.AppendLine($"Request and response bodies do not match at index {i}:");
descriptionBuilder.AppendLine($"Request and record bodies do not match at index {i}:");
var before = Math.Max(0, i - 10);
var afterRequest = Math.Min(i + 20, requestBody.Length);
var afterResponse = Math.Min(i + 20, responseBody.Length);
var afterResponse = Math.Min(i + 20, recordBody.Length);
descriptionBuilder.AppendLine($" request: \"{Encoding.UTF8.GetString(requestBody, before, afterRequest - before)}\"");
descriptionBuilder.AppendLine($" record: \"{Encoding.UTF8.GetString(responseBody, before, afterResponse - before)}\"");
descriptionBuilder.AppendLine($" record: \"{Encoding.UTF8.GetString(recordBody, before, afterResponse - before)}\"");
}
return 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ public class RecordTransport : HttpPipelineTransport
{
private readonly HttpPipelineTransport _innerTransport;

private readonly Func<RecordEntry, EntryRecordModel> _filter;
private readonly Func<RecordEntry, EntryRecordMode> _filter;

private readonly Random _random;

private readonly RecordSession _session;

public RecordTransport(RecordSession session, HttpPipelineTransport innerTransport, Func<RecordEntry, EntryRecordModel> filter, Random random)
public RecordTransport(RecordSession session, HttpPipelineTransport innerTransport, Func<RecordEntry, EntryRecordMode> filter, Random random)
{
_innerTransport = innerTransport;
_filter = filter;
Expand All @@ -50,10 +50,10 @@ private void Record(HttpMessage message)

switch (_filter(recordEntry))
{
case EntryRecordModel.Record:
case EntryRecordMode.Record:
_session.Record(recordEntry);
break;
case EntryRecordModel.RecordWithoutRequestBody:
case EntryRecordMode.RecordWithoutRequestBody:
recordEntry.Request.Body = null;
_session.Record(recordEntry);
break;
Expand Down
4 changes: 2 additions & 2 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy/Playback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ public async Task Start()

if (String.IsNullOrEmpty(file) && !String.IsNullOrEmpty(recordingId))
{
await _recordingHandler.StartPlayback(recordingId, Response, RecordingType.InMemory);
await _recordingHandler.StartPlaybackAsync(recordingId, Response, RecordingType.InMemory);
}
else if(!String.IsNullOrEmpty(file))
{
await _recordingHandler.StartPlayback(file, Response, RecordingType.FilePersisted);
await _recordingHandler.StartPlaybackAsync(file, Response, RecordingType.FilePersisted);
}
else
{
Expand Down
Loading