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

Info/Active/ now offers data for individual sessions #5384

Merged
merged 9 commits into from
Feb 15, 2023
35 changes: 34 additions & 1 deletion tools/test-proxy/Azure.Sdk.Tools.TestProxy.Tests/InfoTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Azure.Sdk.Tools.TestProxy.Common;
using Azure.Sdk.Tools.TestProxy.Common;
using Azure.Sdk.Tools.TestProxy.Matchers;
using Azure.Sdk.Tools.TestProxy.Models;
using Azure.Sdk.Tools.TestProxy.Sanitizers;
using Azure.Sdk.Tools.TestProxy.Transforms;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
Expand Down Expand Up @@ -58,5 +60,36 @@ public void TestReflectionModelWithAdvancedType()

var result = controller.Active();
}

[Fact]
public async Task TestReflectionModelWithTargetRecordSession()
{
RecordingHandler testRecordingHandler = new RecordingHandler(Directory.GetCurrentDirectory());
var httpContext = new DefaultHttpContext();

await testRecordingHandler.StartPlaybackAsync("Test.RecordEntries/multipart_request.json", httpContext.Response);
testRecordingHandler.Transforms.Clear();

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

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

var model = new ActiveMetadataModel(testRecordingHandler, recordingId);
var descriptions = model.Descriptions.ToList();

// we should have exactly 6 if we're counting all the customizations appropriately
Assert.True(descriptions.Count == 6);
Assert.True(model.Matchers.Count() == 1);
Assert.True(model.Sanitizers.Count() == 5);

// confirm that the overridden matcher is showing up
Assert.True(descriptions[3].ConstructorDetails.Arguments[1].Item2 == "\"ABC123\"");
Assert.True(descriptions[4].ConstructorDetails.Arguments[1].Item2 == "\".+?\"");

// and finally confirm our sanitizers are what we expect
Assert.True(descriptions[5].Name == "CustomDefaultMatcher");
}
}
}
22 changes: 17 additions & 5 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy/Info.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Sdk.Tools.TestProxy.Common.Exceptions;
using Azure.Sdk.Tools.TestProxy.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
Expand Down Expand Up @@ -33,15 +34,26 @@ public async Task<ContentResult> Available()
}

[HttpGet]
public async Task<ContentResult> Active()
public async Task<ContentResult> Active(string id="")
{
var dataModel = new ActiveMetadataModel(_recordingHandler);
var viewHtml = await RenderViewAsync(this, "ActiveExtensions", dataModel);
string content = string.Empty;

try
{
var dataModel = new ActiveMetadataModel(_recordingHandler, recordingId: id);
content = await RenderViewAsync(this, "ActiveExtensions", dataModel);
}
// if an httpException is thrown, we have passed in an invalid recordingId, otherwise it'll be an unhandled
// exception, which the exception middleware should surface just fine.
catch (HttpException)
{
content = await RenderViewAsync(this, "Error", new ActiveMetadataModel(id));
}

return new ContentResult
{
ContentType = "text/html",
Content = viewHtml
Content = content
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,79 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml;
using System.IO;
using Azure.Sdk.Tools.TestProxy.Common;
using System.Collections.Concurrent;
using Microsoft.CodeAnalysis.Operations;
using Azure.Sdk.Tools.TestProxy.Common.Exceptions;

namespace Azure.Sdk.Tools.TestProxy.Models
{
public class ActiveMetadataModel : RunTimeMetaDataModel
{
public ActiveMetadataModel(string recordingId)
{
RecordingId = recordingId;
}

public ActiveMetadataModel(RecordingHandler pageRecordingHandler)
{
Descriptions = _populateFromHandler(pageRecordingHandler);
Descriptions = _populateFromHandler(pageRecordingHandler, "");
}

private List<ActionDescription> _populateFromHandler(RecordingHandler handler)
public ActiveMetadataModel(RecordingHandler pageRecordingHandler, string recordingId)
{
RecordingId = recordingId;
Descriptions = _populateFromHandler(pageRecordingHandler, recordingId);
}

public string RecordingId { get; set; }

private List<ActionDescription> _populateFromHandler(RecordingHandler handler, string recordingId)
{
var sanitizers = (IEnumerable<RecordedTestSanitizer>) handler.Sanitizers;
var transforms = (IEnumerable<ResponseTransform>) handler.Transforms;
var matcher = handler.Matcher;

List<ConcurrentDictionary<string, ModifiableRecordSession>> searchCollections = new List<ConcurrentDictionary<string, ModifiableRecordSession>>()
{
handler.PlaybackSessions,
handler.RecordingSessions,
handler.InMemorySessions
};

var recordingFound = false;
if (!string.IsNullOrWhiteSpace(recordingId)){
foreach (var sessionDict in searchCollections)
{
if (sessionDict.TryGetValue(recordingId, out var session))
{
sanitizers = sanitizers.Concat(session.AdditionalSanitizers);
transforms = transforms.Concat(session.AdditionalTransforms);

if (session.CustomMatcher != null)
{
matcher = session.CustomMatcher;
}

recordingFound = true;
break;
}
}

if (!recordingFound)
{
throw new HttpException(System.Net.HttpStatusCode.BadRequest, $"{recordingId} is not found in any Playback, Recording, or In-Memory sessions.");
scbedd marked this conversation as resolved.
Show resolved Hide resolved
}
}

List<ActionDescription> descriptions = new List<ActionDescription>();
var docXML = GetDocCommentXML();

descriptions.AddRange(handler.Sanitizers.Select(x => new ActionDescription()
descriptions.AddRange(sanitizers.Select(x => new ActionDescription()
{
ActionType = MetaDataType.Sanitizer,
Name = x.GetType().Name,
Expand All @@ -39,9 +92,9 @@ private List<ActionDescription> _populateFromHandler(RecordingHandler handler)
descriptions.Add(new ActionDescription()
{
ActionType = MetaDataType.Matcher,
Name = handler.Matcher.GetType().Name,
ConstructorDetails = GetInstanceDetails(handler.Matcher),
Description = GetClassDocComment(handler.Matcher.GetType(), docXML)
Name = matcher.GetType().Name,
ConstructorDetails = GetInstanceDetails(matcher),
Description = GetClassDocComment(matcher.GetType(), docXML)
});

return descriptions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,32 +76,39 @@ public CtorDescription GetInstanceDetails(object target)
var arguments = new List<Tuple<string, string>>();

var filteredFields = fields.Where(x => x.FieldType.Name == "String" || x.FieldType.Name == "ApplyCondition");
foreach (FieldInfo field in filteredFields)

// we only want to crawl the fields if it is an inherited type. customizations are not offered
// when looking at a base RecordMatcher, ResponseTransform, or RecordedTestSanitizer
// These 3 will have a basetype of Object
if (tType.BaseType != typeof(Object))
{
var prop = field.GetValue(target);
string propValue;
if(prop == null)
{
propValue = "This argument is unset or null.";
}
else
foreach (FieldInfo field in filteredFields)
{
if(field.FieldType.Name == "ApplyCondition")
var prop = field.GetValue(target);
string propValue;
if (prop == null)
{
propValue = prop.ToString();

if(propValue == null)
{
continue;
}
propValue = "This argument is unset or null.";
}
else
{
propValue = "\"" + prop.ToString() + "\"";
if (field.FieldType.Name == "ApplyCondition")
{
propValue = prop.ToString();

if (propValue == null)
{
continue;
}
}
else
{
propValue = "\"" + prop.ToString() + "\"";
}
}

arguments.Add(new Tuple<string, string>(field.Name, propValue));
}

arguments.Add(new Tuple<string, string>(field.Name, propValue));
}

return new CtorDescription()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5001/",
"sslPort": 44362
}
},
"profiles": {
"Azure.Sdk.Tools.TestProxy": {
"commandName": "Project",
Expand All @@ -16,5 +8,13 @@
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
scbedd marked this conversation as resolved.
Show resolved Hide resolved
}
},
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5001/",
"sslPort": 44362
}
}
}
14 changes: 13 additions & 1 deletion tools/test-proxy/Azure.Sdk.Tools.TestProxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- [A note about where sanitizers apply](#a-note-about-where-sanitizers-apply)
- [For Sanitizers, Matchers, or Transforms in general](#for-sanitizers-matchers-or-transforms-in-general)
- [Viewing available/active Sanitizers, Matchers, and Transforms](#viewing-availableactive-sanitizers-matchers-and-transforms)
- [To see customizations on a specific recording](#to-see-customizations-on-a-specific-recording)
- [Resetting active Sanitizers, Matchers, and Transforms](#resetting-active-sanitizers-matchers-and-transforms)
- [Reset the session](#reset-the-session)
- [Reset for a specific recordingId](#reset-for-a-specific-recordingid)
Expand Down Expand Up @@ -456,10 +457,21 @@ Currently, the configured set of transforms/playback/sanitizers are NOT propogat
Launch the test-proxy through your chosen method, then visit:

- `<proxyUrl>/Info/Available` to see all available
- `<proxyUrl>/Info/Active` to see all currently active.
- `<proxyUrl>/Info/Active` to see all currently active for all sessions.

Note that the `constructor arguments` that are documented must be present (where documented as such) in the body of the POST sent to the Admin Interface.

#### To see customizations on a specific recording

This only works **while a session is available**. A specific session is only available _before_ it has been stopped. Once that happens it has been written to disk or evacuated from server memory.

Example flow

- Start playback for "hello_world.json"
- Receive a recordingId back
- Place a breakpoint **before** the part of your code that calls `/Record/Stop` or `Playback/Stop`.
- Visit the url `<proxyUrl>/Info/Active?id=<your-recording-id>`

### Resetting active Sanitizers, Matchers, and Transforms

Given that the test-proxy offers the ability to set up customizations for an entire session or a single recording, it also must provide the ability to **reset** these settings without entirely restarting the server.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@using Azure.Sdk.Tools.TestProxy.Models;
@using Azure.Sdk.Tools.TestProxy.Models;
@model ActiveMetadataModel

@{
Expand All @@ -14,12 +14,33 @@
@await Html.PartialAsync("css.cshtml")
</head>
<body>
<h1>
Test-Proxy - Active Extensions
</h1>
<p>
The below extensions are currently configured.
</p>
@if (!string.IsNullOrWhiteSpace(Model.RecordingId))
{
<h1>
Active Extensions for @Model.RecordingId
</h1>
}
else
{
<h1>
Active Extensions for All Sessions
</h1>
}


@if (!string.IsNullOrWhiteSpace(Model.RecordingId))
{
<p>
The below extensions are configured for recording <b>@Model.RecordingId</b>.
</p>
}
else
{
<p>
The below extensions are currently configured for all sessions.
</p>
}

<p>
To observe ALL extensions (rather than just what is currently active), visit <a href="/Info/Available">/Info/Available</a>. For clarity, note that argument values below are surrounded in quotes. The actual value itself being utilized by
the test-proxy does not contain these quotes.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@using Azure.Sdk.Tools.TestProxy.Models;
@model ActiveMetadataModel

@{
Layout = null;
}

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>An error has occured.</title>

@await Html.PartialAsync("css.cshtml")
</head>
<body>
<h1>
Unable to locate @Model.RecordingId.
</h1>

<p>
Unfortunately, a recording id with value "@Model.RecordingId" can not be located in currently active Playback, Recording, or In-Memory sessions.
</p>

<p>
Please confirm that the recording-id in question has <b>NOT</b> been stopped yet. Once a record/playback session has ended, the customizations for that particular session can no longer be retrieved.
</p>
</body>
</html>