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

Update test project to consume framework for recorded tests #19171

Merged
6 changes: 5 additions & 1 deletion common/ManagementTestShared/ResourceGroupCleanupPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Azure.ResourceManager.TestFramework
{
public class ResourceGroupCleanupPolicy : HttpPipelineSynchronousPolicy
{
private readonly object _listLock = new object();
private Regex _resourceGroupPattern = new Regex(@"/subscriptions/[^/]+/resourcegroups/([^?/]+)\?api-version");
private readonly IList<string> _resourceGroupCreated = new List<string>();

Expand All @@ -26,7 +27,10 @@ public override void OnSendingRequest(HttpMessage message)
var match = _resourceGroupPattern.Match(message.Request.Uri.ToString());
if (match.Success)
{
_resourceGroupCreated.Add(match.Groups[1].Value);
lock (_listLock)
{
_resourceGroupCreated.Add(match.Groups[1].Value);
}
}
}
}
Expand Down
129 changes: 129 additions & 0 deletions common/ManagementTestShared/Track2MgmtRecordedTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core;
using Azure.Core.TestFramework;
using Azure.ResourceManager.Core;
using NUnit.Framework;
using System;
using System.Threading.Tasks;

namespace Azure.ResourceManager.TestFramework
{
public abstract class Track2ManagementRecordedTestBase<TEnvironment> : RecordedTestBase<TEnvironment>
where TEnvironment: TestEnvironment, new()
{
protected ResourceGroupCleanupPolicy CleanupPolicy = new ResourceGroupCleanupPolicy();

protected ResourceGroupCleanupPolicy OneTimeCleanupPolicy = new ResourceGroupCleanupPolicy();

protected static AzureResourceManagerClient GlobalClient { get; private set; }

public TestEnvironment SessionEnvironment { get; private set; }

public TestRecording SessionRecording { get; private set; }

private AzureResourceManagerClient _cleanupClient;

protected Track2ManagementRecordedTestBase(bool isAsync) : base(isAsync)
{
SessionEnvironment = new TEnvironment();
SessionEnvironment.Mode = Mode;
}

protected Track2ManagementRecordedTestBase(bool isAsync, RecordedTestMode mode) : base(isAsync, mode)
{
SessionEnvironment = new TEnvironment();
SessionEnvironment.Mode = Mode;
}

private AzureResourceManagerClient GetCleanupClient()
{
if (Mode != RecordedTestMode.Playback)
{
return new AzureResourceManagerClient(
SessionEnvironment.SubscriptionId,
SessionEnvironment.Credential,
new AzureResourceManagerClientOptions());
}
return null;
}

protected AzureResourceManagerClient GetArmClient()
{
var options = InstrumentClientOptions(new AzureResourceManagerClientOptions());
options.AddPolicy(CleanupPolicy, HttpPipelinePosition.PerCall);

return CreateClient<AzureResourceManagerClient>(
TestEnvironment.SubscriptionId,
TestEnvironment.Credential,
options);
}

[TearDown]
protected void CleanupResourceGroups()
{
if (Mode != RecordedTestMode.Playback)
{
Parallel.ForEach(CleanupPolicy.ResourceGroupsCreated, resourceGroup =>
{
_cleanupClient.GetResourceGroupOperations(TestEnvironment.SubscriptionId, resourceGroup).StartDelete();
});
}
}

private void StartSessionRecording()
{
// Only create test recordings for the latest version of the service
TestContext.TestAdapter test = TestContext.CurrentContext.Test;
if (Mode != RecordedTestMode.Live &&
test.Properties.ContainsKey("SkipRecordings"))
{
throw new IgnoreException((string)test.Properties.Get("SkipRecordings"));
}
SessionRecording = new TestRecording(Mode, GetSessionFilePath(), Sanitizer, Matcher);
SessionEnvironment.SetRecording(SessionRecording);
ValidateClientInstrumentation = SessionRecording.HasRequests;
}

private void StopSessionRecording()
{
if (ValidateClientInstrumentation)
{
throw new InvalidOperationException("The test didn't instrument any clients but had recordings. Please call InstrumentClient for the client being recorded.");
}

SessionRecording?.Dispose(true);
}

[OneTimeSetUp]
public void OneTimeSetUp()
{
StartSessionRecording();

_cleanupClient = GetCleanupClient();

var options = InstrumentClientOptions(new AzureResourceManagerClientOptions(), SessionRecording);
options.AddPolicy(OneTimeCleanupPolicy, HttpPipelinePosition.PerCall);

GlobalClient = CreateClient<AzureResourceManagerClient>(
SessionEnvironment.SubscriptionId,
SessionEnvironment.Credential,
options);
}

[OneTimeTearDown]
public void OneTimeCleanupResourceGroups()
{
StopSessionRecording();

if (Mode != RecordedTestMode.Playback)
{
Parallel.ForEach(OneTimeCleanupPolicy.ResourceGroupsCreated, resourceGroup =>
{
_cleanupClient.GetResourceGroupOperations(resourceGroup).StartDelete();
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public void Intercept(IInvocation invocation)
// We don't want to instrument generated rest clients.
if ((type.Name.EndsWith("Client") && !type.Name.EndsWith("RestClient")) ||
// Generated ARM clients will have a property containing the sub-client that ends with Operations.
(invocation.Method.Name.StartsWith("get_") && type.Name.EndsWith("Operations")))
(invocation.Method.Name.StartsWith("get_") && type.Name.EndsWith("Operations")) ||
(type.Name.EndsWith("DefaultSubscription")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can, please, add a comment here about why we need this condition

{
invocation.ReturnValue = _testBase.InstrumentClient(type, result, Array.Empty<IInterceptor>());
}
Expand Down
9 changes: 5 additions & 4 deletions sdk/core/Azure.Core.TestFramework/src/RecordedTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ protected RecordedTestBase(bool isAsync, RecordedTestMode mode) : base(isAsync)
Mode = mode;
}

public T InstrumentClientOptions<T>(T clientOptions) where T : ClientOptions
public T InstrumentClientOptions<T>(T clientOptions, TestRecording recording = default) where T : ClientOptions
{
clientOptions.Transport = Recording.CreateTransport(clientOptions.Transport);
recording ??= Recording;
clientOptions.Transport = recording.CreateTransport(clientOptions.Transport);
if (Mode == RecordedTestMode.Playback)
{
// Not making the timeout zero so retry code still goes async
Expand All @@ -80,7 +81,7 @@ public T InstrumentClientOptions<T>(T clientOptions) where T : ClientOptions
return clientOptions;
}

private string GetSessionFilePath()
protected string GetSessionFilePath()
{
TestContext.TestAdapter testAdapter = TestContext.CurrentContext.Test;

Expand Down Expand Up @@ -141,7 +142,7 @@ public virtual void StartTestRecording()
if (Mode != RecordedTestMode.Live &&
test.Properties.ContainsKey("SkipRecordings"))
{
throw new IgnoreException((string) test.Properties.Get("SkipRecordings"));
throw new IgnoreException((string)test.Properties.Get("SkipRecordings"));
}
Recording = new TestRecording(Mode, GetSessionFilePath(), Sanitizer, Matcher);
ValidateClientInstrumentation = Recording.HasRequests;
Expand Down
5 changes: 5 additions & 0 deletions sdk/core/Azure.Core.TestFramework/src/TestEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ protected string GetOptionalVariable(string name)
value = Environment.GetEnvironmentVariable(name);
}

if (value == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to support the "AZURE_" prefix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so that it can read the regular defaultcredential env variables locally. They are the exact same variables just with AZURE_ prepended so I added that to the check list

{
value = Environment.GetEnvironmentVariable($"AZURE_{name}");
}

if (value == null)
{
_environmentFile.TryGetValue(name, out value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public class AzureResourceManagerClient
/// Initializes a new instance of the <see cref="AzureResourceManagerClient"/> class for mocking.
/// </summary>
protected AzureResourceManagerClient()
: this(null, null, new DefaultAzureCredential(), new AzureResourceManagerClientOptions())
{
}

Expand Down Expand Up @@ -101,17 +100,17 @@ private AzureResourceManagerClient(string defaultSubscriptionId, Uri baseUri, To
/// <summary>
/// Gets the Api version overrides.
/// </summary>
public Dictionary<string, string> ApiVersionOverrides { get; private set; }
public virtual Dictionary<string, string> ApiVersionOverrides { get; private set; }

/// <summary>
/// Gets the default Azure subscription.
/// </summary>
public Subscription DefaultSubscription { get; private set; }
public virtual Subscription DefaultSubscription { get; private set; }

/// <summary>
/// Gets the Azure resource manager client options.
/// </summary>
internal AzureResourceManagerClientOptions ClientOptions { get; }
internal virtual AzureResourceManagerClientOptions ClientOptions { get; }

/// <summary>
/// Gets the Azure subscription operations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@
<None Update="ScenarioTests\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestAssets\Identity\*.json">
<None Update="Unit\TestAssets\Identity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestAssets\UserAssignedIdentity\*.json">
<None Update="Unit\TestAssets\UserAssignedIdentity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestAssets\SystemAssignedIdentity\*.json">
<None Update="Unit\TestAssets\SystemAssignedIdentity\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="SessionRecords\SubscriptionOperationsTests\" />
<Folder Include="SessionRecords\TaggableResourceTests\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core.TestFramework;
using Azure.ResourceManager.TestFramework;

namespace Azure.ResourceManager.Core.Tests
m-nash marked this conversation as resolved.
Show resolved Hide resolved
{
public class ResourceManagerTestBase : Track2ManagementRecordedTestBase<ResourceManagerTestEnvironment>
{
protected ResourceManagerTestBase(bool isAsync, RecordedTestMode mode)
: base(isAsync, mode)
{
}

protected ResourceManagerTestBase(bool isAsync)
: base(isAsync)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core.TestFramework;

namespace Azure.ResourceManager.Core.Tests
{
public class ResourceManagerTestEnvironment : TestEnvironment
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text;
using Azure.Core.TestFramework;
using NUnit.Framework;

namespace Azure.ResourceManager.Core.Tests
{
public class SubscriptionOperationsTests : ResourceManagerTestBase
{
private AzureResourceManagerClient _client;

public SubscriptionOperationsTests(bool isAsync)
: base(isAsync)//, RecordedTestMode.Record)
{
}

[SetUp]
public void SetUp()
{
_client = GetArmClient();
}

[TestCase(null)]
[TestCase("")]
[SyncOnly]
[RecordedTest]
public void TestGetResourceGroupOpsArgNullException(string resourceGroupName)
{
var subOps = _client.DefaultSubscription;
Assert.Throws<ArgumentOutOfRangeException>(delegate { subOps.GetResourceGroupOperations(resourceGroupName); });
}

[TestCase("te%st")]
[TestCase("test ")]
[TestCase("te$st")]
[TestCase("te#st")]
[TestCase("te#st")]
[SyncOnly]
[RecordedTest]
public void TestGetResourceGroupOpsArgException(string resourceGroupName)
{
var subOps = _client.DefaultSubscription;
Assert.Throws<ArgumentException>(delegate { subOps.GetResourceGroupOperations(resourceGroupName); });
}

[TestCase(91)]
[SyncOnly]
[RecordedTest]
public void TestGetResourceGroupOpsOutOfRangeArgException(int length)
{
var resourceGroupName = GetLongString(length);
var subOps = _client.DefaultSubscription;
Assert.Throws<ArgumentOutOfRangeException>(delegate { subOps.GetResourceGroupOperations(resourceGroupName); });
}

[TestCase("te.st")]
[TestCase("te")]
[TestCase("t")]
[SyncOnly]
[RecordedTest]
public void TestGetResourceGroupOpsValid(string resourceGroupName)
{
var subOps = _client.DefaultSubscription;
Assert.DoesNotThrow(delegate { subOps.GetResourceGroupOperations(resourceGroupName); });
}

[TestCase(89)]
[TestCase(90)]
[SyncOnly]
[RecordedTest]
public void TestGetResourceGroupOpsLong(int length)
{
var resourceGroupName = GetLongString(length);
var subOps = _client.DefaultSubscription;
Assert.DoesNotThrow(delegate { subOps.GetResourceGroupOperations(resourceGroupName); });
}

private string GetLongString(int length)
{
StringBuilder builder = new StringBuilder();
for(int i=0; i<length; i++)
{
builder.Append('a');
}
return builder.ToString();
}
}
}
Loading