Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
15e6946
begin the refactor. need to update wherever we touch sanitizers to en…
scbedd Apr 12, 2024
1f34c08
just save away the progress so that this can be pulled over to anothe…
scbedd Apr 17, 2024
13d6486
making a sanitizer store its id so that we have a consistent value to…
scbedd Apr 17, 2024
b861e01
getting closer. Need to rationalize registration + removal
scbedd Apr 17, 2024
e2304aa
first passing build. now time to discover all the test failures. gon …
scbedd Apr 18, 2024
45332b9
gotta initialize the Sanitizers concurrentDictionary!
scbedd Apr 18, 2024
c9bde0c
all but 3 tests passing
scbedd Apr 18, 2024
2ece462
simplify _register a bit
scbedd Apr 18, 2024
9a16d3e
rid ourselves of csharp9 call
scbedd Apr 18, 2024
8e2a873
committing progress w/ incomplete new routes
scbedd Apr 18, 2024
b92dec5
reference a specific branch for harsha to explore on
scbedd Apr 18, 2024
a1ae82e
remove some usings
scbedd Apr 18, 2024
053ce5e
we are getting a ton closer
scbedd Apr 18, 2024
95cb685
attempt interlocked increment with the automatic id
scbedd Apr 19, 2024
ef69312
further feedback to avoid ever having a problem
scbedd Apr 19, 2024
c77d722
skin out the tests
scbedd Apr 19, 2024
a3d7d17
repair admin test
scbedd Apr 19, 2024
755d0b2
forgot to set content length woops
scbedd Apr 19, 2024
a40939a
now we're properly writing the responses from Admin
scbedd Apr 19, 2024
2dde663
actually fix the first test
scbedd Apr 19, 2024
518066b
we can actually remove sanitizers now
scbedd Apr 19, 2024
d52c08e
we can remove sanitizers by route now. time to test
scbedd Apr 20, 2024
4cb7012
now it is connected top to bottom. tests only remain
scbedd Apr 20, 2024
f6fb3ce
adding the tests and catching a bug in recording-specific sanitizer r…
scbedd Apr 22, 2024
75fcad2
small correction on returned result
scbedd Apr 22, 2024
02cd858
Update tools/test-proxy/Azure.Sdk.Tools.TestProxy/Properties/launchSe…
scbedd Apr 22, 2024
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
301 changes: 264 additions & 37 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy.Tests/AdminTests.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public void TestReflectionModelWithAdvancedType()
{
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
var httpContext = new DefaultHttpContext();
testRecordingHandler.Sanitizers.Clear();
testRecordingHandler.Sanitizers.Add(new GeneralRegexSanitizer(value: "A new value", condition: new ApplyCondition() { UriRegex= ".+/Tables" }));
testRecordingHandler.SanitizerRegistry.Clear();
testRecordingHandler.SanitizerRegistry.Register(new GeneralRegexSanitizer(value: "A new value", condition: new ApplyCondition() { UriRegex= ".+/Tables" }));

var controller = new Info(testRecordingHandler)
{
Expand All @@ -72,8 +72,8 @@ public async Task TestReflectionModelWithTargetRecordSession()

var recordingId = httpContext.Response.Headers["x-recording-id"].ToString();

testRecordingHandler.AddSanitizerToRecording(recordingId, new UriRegexSanitizer(regex: "ABC123"));
testRecordingHandler.AddSanitizerToRecording(recordingId, new BodyRegexSanitizer(regex: ".+?"));
testRecordingHandler.RegisterSanitizer(new UriRegexSanitizer(regex: "ABC123"), recordingId);
testRecordingHandler.RegisterSanitizer(new BodyRegexSanitizer(regex: ".+?"), recordingId);
testRecordingHandler.SetMatcherForRecording(recordingId, new CustomDefaultMatcher(compareBodies: false, excludedHeaders: "an-excluded-header"));

var model = new ActiveMetadataModel(testRecordingHandler, recordingId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task PlaybackLogsSanitizedRequest()
Assert.True(testRecordingHandler.PlaybackSessions.ContainsKey(recordingId));
var entry = testRecordingHandler.PlaybackSessions[recordingId].Session.Entries[0];
HttpRequest request = TestHelpers.CreateRequestFromEntry(entry);
request.Headers["Authorization"] = "fake-auth-header";
request.Headers["Authorization"] = "Sanitized";

HttpResponse response = new DefaultHttpContext().Response;
await testRecordingHandler.HandlePlaybackRequest(recordingId, request, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Runtime.InteropServices;
using Azure.Sdk.Tools.TestProxy.Common.Exceptions;
using Azure.Sdk.Tools.TestProxy.Store;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;

namespace Azure.Sdk.Tools.TestProxy.Tests
{
Expand Down Expand Up @@ -96,10 +97,11 @@ private void _checkDefaultExtensions(RecordingHandler handlerForTest, CheckSkips

if (skipsToCheck.HasFlag(CheckSkips.IncludeSanitizers))
{
Assert.Equal(3, handlerForTest.Sanitizers.Count);
Assert.IsType<RecordedTestSanitizer>(handlerForTest.Sanitizers[0]);
Assert.IsType<BodyKeySanitizer>(handlerForTest.Sanitizers[1]);
Assert.IsType<BodyKeySanitizer>(handlerForTest.Sanitizers[2]);
var sessionSanitizers = handlerForTest.SanitizerRegistry.GetSanitizers();
Assert.Equal(3, sessionSanitizers.Count);
Assert.IsType<RecordedTestSanitizer>(sessionSanitizers[0]);
Assert.IsType<BodyKeySanitizer>(sessionSanitizers[1]);
Assert.IsType<BodyKeySanitizer>(sessionSanitizers[2]);
}
}
#endregion
Expand Down Expand Up @@ -166,7 +168,7 @@ public void TestResetAfterAddition()
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());

// act
testRecordingHandler.Sanitizers.Add(new BodyRegexSanitizer("sanitized", ".*"));
testRecordingHandler.SanitizerRegistry.Register(new BodyRegexSanitizer("sanitized", ".*"));
testRecordingHandler.Matcher = new BodilessMatcher();
testRecordingHandler.Transforms.Add(new ApiVersionTransform());
testRecordingHandler.SetDefaultExtensions();
Expand All @@ -182,7 +184,7 @@ public void TestResetAfterRemoval()
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());

// act
testRecordingHandler.Sanitizers.Clear();
testRecordingHandler.SanitizerRegistry.Clear();
testRecordingHandler.Matcher = null;
testRecordingHandler.Transforms.Clear();
testRecordingHandler.SetDefaultExtensions();
Expand All @@ -199,18 +201,19 @@ public async Task TestResetTargetsRecordingOnly()
await testRecordingHandler.StartRecordingAsync("recordingings/cool.json", httpContext.Response);
var recordingId = httpContext.Response.Headers["x-recording-id"].ToString();


testRecordingHandler.Sanitizers.Clear();
testRecordingHandler.Sanitizers.Add(new BodyRegexSanitizer("sanitized", ".*"));
testRecordingHandler.AddSanitizerToRecording(recordingId, new GeneralRegexSanitizer("sanitized", ".*"));
testRecordingHandler.SanitizerRegistry.Clear();
testRecordingHandler.SanitizerRegistry.Register(new BodyRegexSanitizer("sanitized", ".*"));
testRecordingHandler.RegisterSanitizer(new GeneralRegexSanitizer("sanitized", ".*"), recordingId);
testRecordingHandler.SetDefaultExtensions(recordingId);
var session = testRecordingHandler.RecordingSessions.First().Value;
var recordingSanitizers = testRecordingHandler.SanitizerRegistry.GetSanitizers(session);
var sessionSanitizers = testRecordingHandler.SanitizerRegistry.GetSanitizers();

// session sanitizer is still set to a single one
Assert.Single(testRecordingHandler.Sanitizers);
Assert.IsType<BodyRegexSanitizer>(testRecordingHandler.Sanitizers[0]);
Assert.Single(sessionSanitizers);
Assert.IsType<BodyRegexSanitizer>(sessionSanitizers[0]);
_checkDefaultExtensions(testRecordingHandler, CheckSkips.IncludeMatcher | CheckSkips.IncludeTransforms);
Assert.Empty(session.AdditionalSanitizers);
Assert.Equal(session.AppliedSanitizers, testRecordingHandler.SanitizerRegistry.SessionSanitizers);
Assert.Empty(session.AdditionalTransforms);
Assert.Null(session.CustomMatcher);
}
Expand All @@ -223,22 +226,22 @@ public async Task TestResetTargetsSessionOnly()
await testRecordingHandler.StartRecordingAsync("recordingings/cool.json", httpContext.Response);
var recordingId = httpContext.Response.Headers["x-recording-id"].ToString();

testRecordingHandler.Sanitizers.Clear();
testRecordingHandler.Sanitizers.Add(new BodyRegexSanitizer("sanitized", ".*"));
testRecordingHandler.SanitizerRegistry.Clear();
testRecordingHandler.SanitizerRegistry.Register(new BodyRegexSanitizer("sanitized", ".*"));
testRecordingHandler.Transforms.Clear();
testRecordingHandler.AddSanitizerToRecording(recordingId, new GeneralRegexSanitizer("sanitized", ".*"));
testRecordingHandler.RegisterSanitizer(new GeneralRegexSanitizer("sanitized", ".*"), recordingId);
testRecordingHandler.SetDefaultExtensions(recordingId);
var session = testRecordingHandler.RecordingSessions.First().Value;

// check that the individual session had reset sanitizers
Assert.Empty(session.AdditionalSanitizers);
Assert.Equal(testRecordingHandler.SanitizerRegistry.GetSanitizers(), testRecordingHandler.SanitizerRegistry.GetSanitizers(session));

// stop the recording to clear out the session cache
testRecordingHandler.StopRecording(recordingId);

// then verify that the session level is NOT reset.
Assert.Single(testRecordingHandler.Sanitizers);
Assert.IsType<BodyRegexSanitizer>(testRecordingHandler.Sanitizers.First());
Assert.Single(testRecordingHandler.SanitizerRegistry.GetSanitizers());
Assert.IsType<BodyRegexSanitizer>(testRecordingHandler.SanitizerRegistry.GetSanitizers().First());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public async Task RegexEntrySanitizerCreatesOverAPI(string body)
{

RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
testRecordingHandler.Sanitizers.Clear();
testRecordingHandler.SanitizerRegistry.Clear();
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["x-abstraction-identifier"] = "RegexEntrySanitizer";
httpContext.Request.Body = TestHelpers.GenerateStreamRequestBody(body);
Expand All @@ -142,7 +142,7 @@ public async Task RegexEntrySanitizerCreatesOverAPI(string body)
};

await controller.AddSanitizer();
var sanitizer = testRecordingHandler.Sanitizers[0];
var sanitizer = testRecordingHandler.SanitizerRegistry.GetSanitizers()[0];
Assert.True(sanitizer is RegexEntrySanitizer);


Expand Down
23 changes: 21 additions & 2 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy.Tests/TestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ public static ModifiableRecordSession LoadRecordSession(string path)
using var stream = System.IO.File.OpenRead(path);
using var doc = JsonDocument.Parse(stream);

return new ModifiableRecordSession(RecordSession.Deserialize(doc.RootElement));
return new ModifiableRecordSession(RecordSession.Deserialize(doc.RootElement), new SanitizerDictionary(), Guid.NewGuid().ToString());
}

public static RecordingHandler LoadRecordSessionIntoInMemoryStore(string path)
{
using var stream = System.IO.File.OpenRead(path);
using var doc = JsonDocument.Parse(stream);
var guid = Guid.NewGuid().ToString();
var session = new ModifiableRecordSession(RecordSession.Deserialize(doc.RootElement));
var session = new ModifiableRecordSession(RecordSession.Deserialize(doc.RootElement), new SanitizerDictionary(), Guid.NewGuid().ToString());

RecordingHandler handler = new RecordingHandler(Directory.GetCurrentDirectory());
handler.InMemorySessions.TryAdd(guid, session);
Expand Down Expand Up @@ -516,5 +516,24 @@ public static bool CheckExistenceOfTag(Assets assets, string workingDirectory)
CommandResult result = GitHandler.Run($"ls-remote {cloneUrl} --tags {assets.Tag}", workingDirectory);
return result.StdOut.Trim().Length > 0;
}

public static List<T> EnumerateArray<T>(JsonElement element)
{
List<T> values = new List<T>();

if (element.ValueKind.ToString() != "Array")
{
throw new Exception("This test helper is intended for array members only");
}
else
{
foreach(var item in element.EnumerateArray())
{
values.Add(item.Deserialize<T>());
}
}

return values;
}
}
}
94 changes: 90 additions & 4 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy/Admin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using Azure.Sdk.Tools.TestProxy.Common;
using Azure.Sdk.Tools.TestProxy.Common.Exceptions;
using Azure.Sdk.Tools.TestProxy.Store;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
Expand Down Expand Up @@ -63,6 +65,73 @@ public async Task AddTransform()
}
}

[HttpPost]
public async Task RemoveSanitizers([FromBody]RemoveSanitizerList sanitizerList)
{
DebugLogger.LogAdminRequestDetails(_logger, Request);
var recordingId = RecordingHandler.GetHeader(Request, "x-recording-id", allowNulls: true);

var removedSanitizers = new List<string>();
var exceptionsList = new List<string>();

if (sanitizerList.Sanitizers.Count == 0)
{
throw new HttpException(HttpStatusCode.BadRequest, "At least one sanitizerId for removal must be provided.");
}

foreach(var sanitizerId in sanitizerList.Sanitizers) {
try
{
var removedId = _recordingHandler.UnregisterSanitizer(sanitizerId, recordingId);
removedSanitizers.Add(sanitizerId);
}
catch (HttpException ex) {
exceptionsList.Add(ex.Message);
}
}

if (exceptionsList.Count > 0)
{
var varExceptionMessage = $"Unable to remove {exceptionsList.Count} sanitizer{(exceptionsList.Count > 1 ? 's' : string.Empty)}. Detailed list follows: \n"
+ string.Join("\n", exceptionsList);
throw new HttpException(HttpStatusCode.BadRequest, varExceptionMessage);
}
else
{
var json = JsonSerializer.Serialize(new { Removed = removedSanitizers });

Response.ContentType = "application/json";
Response.ContentLength = json.Length;

await Response.WriteAsync(json);
}
}

[HttpGet]
public async Task GetSanitizers()
{
DebugLogger.LogAdminRequestDetails(_logger, Request);
var recordingId = RecordingHandler.GetHeader(Request, "x-recording-id", allowNulls: true);

List<RegisteredSanitizer> sanitizers;

if (!string.IsNullOrEmpty(recordingId))
{
var session = _recordingHandler.GetActiveSession(recordingId);
sanitizers = _recordingHandler.SanitizerRegistry.GetRegisteredSanitizers(session);
}
else
{
sanitizers = _recordingHandler.SanitizerRegistry.GetRegisteredSanitizers();
}

var json = JsonSerializer.Serialize(new { Sanitizers = sanitizers });
Response.ContentType = "application/json";
Response.ContentLength = json.Length;

await Response.WriteAsync(json);
}

[HttpPost]
public async Task AddSanitizer()
{
Expand All @@ -72,14 +141,22 @@ public async Task AddSanitizer()

RecordedTestSanitizer s = (RecordedTestSanitizer)GetSanitizer(sName, await HttpRequestInteractions.GetBody(Request));

string registeredSanitizerId;

if (recordingId != null)
{
_recordingHandler.AddSanitizerToRecording(recordingId, s);
registeredSanitizerId = _recordingHandler.RegisterSanitizer(s, recordingId);
}
else
{
_recordingHandler.Sanitizers.Add(s);
registeredSanitizerId = _recordingHandler.RegisterSanitizer(s);
}

var json = JsonSerializer.Serialize(new { Sanitizer = registeredSanitizerId });
Response.ContentType = "application/json";
Response.ContentLength = json.Length;

await Response.WriteAsync(json);
}

[HttpPost]
Expand All @@ -96,19 +173,28 @@ public async Task AddSanitizers()
throw new HttpException(HttpStatusCode.BadRequest, "When bulk adding sanitizers, ensure there is at least one sanitizer added in each batch. Received 0 work items.");
}

var registeredSanitizers = new List<string>();

// register them all
foreach(var sanitizer in workload)
{
if (recordingId != null)
{
_recordingHandler.AddSanitizerToRecording(recordingId, sanitizer);
var registeredId = _recordingHandler.RegisterSanitizer(sanitizer, recordingId);
registeredSanitizers.Add(registeredId);
Response.Headers.Add("x-recording-id", recordingId);
}
else
{
_recordingHandler.Sanitizers.Add(sanitizer);
var registeredId = _recordingHandler.RegisterSanitizer(sanitizer);
registeredSanitizers.Add(registeredId);
}
}
var json = JsonSerializer.Serialize(new { Sanitizers = registeredSanitizers });
Response.ContentType = "application/json";
Response.ContentLength = json.Length;

await Response.WriteAsync(json);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,41 @@ public class ModifiableRecordSession

public RecordSession Session { get; }

public ModifiableRecordSession(RecordSession session)
public ModifiableRecordSession(SanitizerDictionary sanitizerRegistry, string sessionId)
{
this.AppliedSanitizers = sanitizerRegistry.SessionSanitizers.ToList();
this.SessionId = sessionId;
}

public ModifiableRecordSession(RecordSession session, SanitizerDictionary sanitizerRegistry, string sessionId)
{
Session = session;
this.AppliedSanitizers = sanitizerRegistry.SessionSanitizers.ToList();
this.SessionId = sessionId;
}

public string SessionId;

public string Path { get; set; }

public HttpClient Client { get; set; }

public List<ResponseTransform> AdditionalTransforms { get; } = new List<ResponseTransform>();

public List<RecordedTestSanitizer> AdditionalSanitizers { get; }= new List<RecordedTestSanitizer>();
public List<string> AppliedSanitizers { get; set; } = new List<string>();
public List<string> ForRemoval { get; } = new List<string>();

public string SourceRecordingId { get; set; }

public int PlaybackResponseTime { get; set; }

public void ResetExtensions()
public void ResetExtensions(SanitizerDictionary sanitizerDictionary)
{
AdditionalTransforms.Clear();
AdditionalSanitizers.Clear();
AppliedSanitizers = new List<string>();
AppliedSanitizers.AddRange(sanitizerDictionary.SessionSanitizers);
ForRemoval.Clear();

CustomMatcher = null;
Client = null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core;
Expand All @@ -21,7 +21,6 @@ public class RecordedTestSanitizer

public ApplyCondition Condition { get; protected set; } = null;


/// This is just a temporary workaround to avoid breaking tests that need to be re-recorded
// when updating the JsonPathSanitizer logic to avoid changing date formats when deserializing requests.
// this property will be removed in the future.
Expand Down
Loading