Skip to content

Commit

Permalink
Refactored tests. Fixed user agent container to give a unique id for …
Browse files Browse the repository at this point in the history
…each client instance.
  • Loading branch information
Jake Willey committed Oct 11, 2019
1 parent 951f285 commit 4b16af5
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 78 deletions.
18 changes: 10 additions & 8 deletions Microsoft.Azure.Cosmos/src/EnvironmentInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;

internal sealed class EnvironmentInformation
{
private static readonly string clientId;
private const int MaxOperatingSystemString = 50;
private static readonly string clientSDKVersion;
private static readonly string directPackageVersion;
private static readonly string framework;
Expand All @@ -25,14 +28,18 @@ static EnvironmentInformation()
EnvironmentInformation.framework = RuntimeInformation.FrameworkDescription;
EnvironmentInformation.architecture = RuntimeInformation.ProcessArchitecture.ToString();
EnvironmentInformation.os = RuntimeInformation.OSDescription;
}

public EnvironmentInformation()
{
string now = DateTime.UtcNow.Ticks.ToString();
EnvironmentInformation.clientId = now.Substring(now.Length - 5); // 5 most significative digits
this.ClientId = now.Substring(now.Length - 5); // 5 most significative digits
}

/// <summary>
/// Unique identifier of a client
/// </summary>
public string ClientId => EnvironmentInformation.clientId;
public string ClientId { get; }

/// <summary>
/// Version of the current direct package.
Expand Down Expand Up @@ -61,10 +68,5 @@ static EnvironmentInformation()
/// </summary>
/// <seealso cref="RuntimeInformation.ProcessArchitecture"/>
public string ProcessArchitecture => EnvironmentInformation.architecture;

public override string ToString()
{
return $"{this.OperatingSystem}|{this.ProcessArchitecture} {this.ClientVersion}|{this.DirectVersion}|{this.RuntimeFramework} {this.ClientId}|";
}
}
}
35 changes: 23 additions & 12 deletions Microsoft.Azure.Cosmos/src/UserAgentContainer.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System.Text.RegularExpressions;

/// <summary>
/// Contains information about the user environment and helps identify requests.
/// </summary>
internal class UserAgentContainer : Documents.UserAgentContainer
{
private static readonly string cosmosBaseUserAgent;

static UserAgentContainer()
{
EnvironmentInformation environmentInformation = new EnvironmentInformation();
string infoString = environmentInformation.ToString();

// Two Backslashes in the same string without a space will cause the HTTP to throw a format exception
UserAgentContainer.cosmosBaseUserAgent = infoString.Replace("/", "-");
}
private const int MaxOperatingSystemString = 30;
private string cosmosBaseUserAgent;

public UserAgentContainer()
: base()
Expand All @@ -29,8 +22,26 @@ internal override string BaseUserAgent
{
get
{
return UserAgentContainer.cosmosBaseUserAgent;
if (this.cosmosBaseUserAgent == null)
{
this.cosmosBaseUserAgent = this.CreateBaseUserAgentString();
}

return this.cosmosBaseUserAgent;
}
}

private string CreateBaseUserAgentString()
{
EnvironmentInformation environmentInformation = new EnvironmentInformation();
string operatingSystem = environmentInformation.OperatingSystem;
if (operatingSystem.Length > MaxOperatingSystemString)
{
operatingSystem = operatingSystem.Substring(0, MaxOperatingSystemString);
}

// Regex replaces all special characters with empty space except . - | since they do not cause format exception for the user agent string.
return Regex.Replace($"cosmos-net|{environmentInformation.ClientVersion}|{environmentInformation.DirectVersion}|{environmentInformation.ClientId}|{environmentInformation.ProcessArchitecture}|{operatingSystem}|", @"[^0-9a-zA-Z\.\|\-]+", " ");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Routing;
Expand Down Expand Up @@ -439,64 +440,6 @@ public void ValidateIfNonMatchRntbd()
ValidateIfNonMatch(client);
}

[TestMethod]
public void ValidateCustomUserAgentHeader()
{
const string suffix = " MyCustomUserAgent/1.0";
ConnectionPolicy policy = new ConnectionPolicy();
policy.UserAgentSuffix = suffix;
var expectedUserAgent = new Cosmos.UserAgentContainer().BaseUserAgent + suffix;
Assert.AreEqual(expectedUserAgent, policy.UserAgentContainer.UserAgent);

byte[] expectedUserAgentUTF8 = Encoding.UTF8.GetBytes(expectedUserAgent);
CollectionAssert.AreEqual(expectedUserAgentUTF8, policy.UserAgentContainer.UserAgentUTF8);
}

[TestMethod]
public async Task ValidateUserAgentHeaderWithCustomOs()
{
const string suffix = " MyCustomUserAgent/1.0";
// "Microsoft Windows 10.0.18362 /X64 3.3.0/3.3.0-.NET Core 4.6.28008.01 91413 MyCustomUserAgent/1.0"
const string invalidOsField = "xnu-4903.271.2~2|RELEASE_X86_64|X64 3.3.0|3.3.0-.NET Core 4.6.27817.03 89710|";
using (CosmosClient client = TestCommon.CreateCosmosClient(builder => builder.WithApplicationName(suffix)))
{
Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer;
FieldInfo fieldInfo = typeof(Cosmos.UserAgentContainer).GetField("cosmosBaseUserAgent", BindingFlags.Static | BindingFlags.NonPublic);
fieldInfo.SetValue(userAgentContainer, invalidOsField);
userAgentContainer.Suffix = suffix;

string userAgentString = userAgentContainer.UserAgent;
Assert.IsTrue(userAgentString.Contains(suffix));
Assert.IsTrue(userAgentString.Contains(invalidOsField));
Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync(Guid.NewGuid().ToString());
Assert.IsNotNull(db);
await db.DeleteAsync();
}
}

[TestMethod]
public void ValidateUserAgent()
{
// Invalid user agent string "A/AA/A";
// "Microsoft Windows 10.0.18362 /X64 3.3.0/3.3.0-.NET Core 4.6.28008.01 91413 MyCustomUserAgent/1.0"
string invalidOsField = "||AAA/A|Test|CB-A-23 Test|";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add(HttpConstants.HttpHeaders.UserAgent, invalidOsField);
}

[TestMethod]
public void ValidateCustomUserAgentContainer()
{
const string suffix = " MyCustomUserAgent/1.0";
UserAgentContainer userAgentContainer = new Cosmos.UserAgentContainer();
userAgentContainer.Suffix = suffix;
string expectedUserAgent = new Cosmos.UserAgentContainer().BaseUserAgent + suffix;
Assert.AreEqual(expectedUserAgent, userAgentContainer.UserAgent);

byte[] expectedUserAgentUTF8 = Encoding.UTF8.GetBytes(expectedUserAgent);
CollectionAssert.AreEqual(expectedUserAgentUTF8, userAgentContainer.UserAgentUTF8);
}

[TestMethod]
public void ValidateVersionHeader()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Routing;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Collections;
using Microsoft.Azure.Documents.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class UserAgentTests
{
[TestMethod]
public void ValidateCustomUserAgentHeader()
{
const string suffix = " MyCustomUserAgent/1.0";
ConnectionPolicy policy = new ConnectionPolicy();
policy.UserAgentSuffix = suffix;
Assert.IsTrue(policy.UserAgentContainer.UserAgent.EndsWith(suffix));

byte[] expectedUserAgentUTF8 = Encoding.UTF8.GetBytes(policy.UserAgentContainer.UserAgent);
CollectionAssert.AreEqual(expectedUserAgentUTF8, policy.UserAgentContainer.UserAgentUTF8);
}

[TestMethod]
public void ValidateUniqueClientIdHeader()
{
using (CosmosClient client = TestCommon.CreateCosmosClient())
{
string firstClientId = this.GetClientIdFromCosmosClient(client);

using (CosmosClient innerClient = TestCommon.CreateCosmosClient())
{
string secondClientId = this.GetClientIdFromCosmosClient(innerClient);
Assert.AreNotEqual(firstClientId, secondClientId);
}
}
}

[TestMethod]
public async Task ValidateUserAgentHeaderWithCustomOs()
{
//This changes the runtime information to simulate a max os x response
const string invalidOsField = "Darwin 18.0.0: Darwin/Kernel/Version 18.0.0: Wed Aug 22 20:13:40 PDT 2018; root:xnu-4903.201.2~1/RELEASE_X86_64";
FieldInfo fieldInfo = typeof(RuntimeInformation).GetField("s_osDescription", BindingFlags.Static | BindingFlags.NonPublic);
fieldInfo.SetValue(null, invalidOsField);
string updatedRuntime = RuntimeInformation.OSDescription;
Assert.AreEqual(invalidOsField, updatedRuntime);

const string suffix = " MyCustomUserAgent/1.0";

using (CosmosClient client = TestCommon.CreateCosmosClient(builder => builder.WithApplicationName(suffix)))
{
Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer;

string userAgentString = userAgentContainer.UserAgent;
Assert.IsTrue(userAgentString.Contains(suffix));
Assert.IsTrue(userAgentString.Contains("Darwin 18.0.0"));
Cosmos.Database db = await client.CreateDatabaseIfNotExistsAsync(Guid.NewGuid().ToString());
Assert.IsNotNull(db);
await db.DeleteAsync();
}
}

private string GetClientIdFromCosmosClient(CosmosClient client)
{
Cosmos.UserAgentContainer userAgentContainer = client.ClientOptions.GetConnectionPolicy().UserAgentContainer;
string userAgentString = userAgentContainer.UserAgent;
string clientId = userAgentString.Split('|')[3];
Assert.AreEqual(5, clientId.Length);
return clientId;
}
}
}

0 comments on commit 4b16af5

Please sign in to comment.