diff --git a/src/Microsoft.Identity.Web.TokenCache/CacheSerializerHints.cs b/src/Microsoft.Identity.Web.TokenCache/CacheSerializerHints.cs index 2ea239e18..ea62cbde9 100644 --- a/src/Microsoft.Identity.Web.TokenCache/CacheSerializerHints.cs +++ b/src/Microsoft.Identity.Web.TokenCache/CacheSerializerHints.cs @@ -3,6 +3,7 @@ using System; using System.Threading; +using Microsoft.Identity.Client.TelemetryCore.TelemetryClient; namespace Microsoft.Identity.Web.TokenCacheProviders { @@ -21,5 +22,10 @@ public class CacheSerializerHints /// with the app token cache. /// public DateTimeOffset? SuggestedCacheExpiry { get; set; } + + /// + /// Stores details to log to MSAL's telemetry client + /// + internal TelemetryData? TelemetryData { get; set; } } } diff --git a/src/Microsoft.Identity.Web.TokenCache/Distributed/MsalDistributedTokenCacheAdapter.cs b/src/Microsoft.Identity.Web.TokenCache/Distributed/MsalDistributedTokenCacheAdapter.cs index f80c40b20..708eb3f4d 100644 --- a/src/Microsoft.Identity.Web.TokenCache/Distributed/MsalDistributedTokenCacheAdapter.cs +++ b/src/Microsoft.Identity.Web.TokenCache/Distributed/MsalDistributedTokenCacheAdapter.cs @@ -147,6 +147,7 @@ await L2OperationWithRetryOnFailureAsync( { const string read = "Read"; byte[]? result = null; + var telemetryData = cacheSerializerHints.TelemetryData; if (_memoryCache != null) { @@ -169,6 +170,11 @@ await L2OperationWithRetryOnFailureAsync( }, cacheSerializerHints.CancellationToken).Measure().ConfigureAwait(false); #pragma warning restore CA1062 // Validate arguments of public methods + if (result != null && telemetryData != null) + { + telemetryData.CacheLevel = Client.Cache.CacheLevel.L2Cache; + } + Logger.DistributedCacheReadTime(_logger, _distributedCacheType, read, measure.MilliSeconds); if (_memoryCache != null) @@ -195,6 +201,11 @@ await L2OperationWithRetryOnFailureAsync( (cacheKey) => _distributedCache.RefreshAsync(cacheKey, cacheSerializerHints.CancellationToken), cacheKey, result!).ConfigureAwait(false); + + if (telemetryData != null) + { + telemetryData.CacheLevel = Client.Cache.CacheLevel.L1Cache; + } } #pragma warning disable CS8603 // Possible null reference return. diff --git a/src/Microsoft.Identity.Web.TokenCache/InMemory/MsalMemoryTokenCacheProvider.cs b/src/Microsoft.Identity.Web.TokenCache/InMemory/MsalMemoryTokenCacheProvider.cs index 1d29b8d5e..e3351f01d 100644 --- a/src/Microsoft.Identity.Web.TokenCache/InMemory/MsalMemoryTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web.TokenCache/InMemory/MsalMemoryTokenCacheProvider.cs @@ -62,6 +62,24 @@ protected override Task RemoveKeyAsync(string cacheKey) return Task.FromResult(tokenCacheBytes); } + /// + /// Method to be overridden by concrete cache serializers to Read the cache bytes. + /// + /// Cache key. + /// Hints for the cache serialization implementation optimization. + /// Read bytes. + protected override Task ReadCacheBytesAsync(string cacheKey, CacheSerializerHints cacheSerializerHints) + { + byte[]? tokenCacheBytes = (byte[]?)_memoryCache.Get(cacheKey); + + if (tokenCacheBytes != null && cacheSerializerHints.TelemetryData != null) + { + cacheSerializerHints.TelemetryData.CacheLevel = Client.Cache.CacheLevel.L1Cache; + } + + return Task.FromResult(tokenCacheBytes); + } + /// /// Writes a token cache blob to the serialization cache (identified by its key). /// diff --git a/src/Microsoft.Identity.Web.TokenCache/MsalAbstractTokenCacheProvider.cs b/src/Microsoft.Identity.Web.TokenCache/MsalAbstractTokenCacheProvider.cs index 88f2fe8a2..1fcd74ff2 100644 --- a/src/Microsoft.Identity.Web.TokenCache/MsalAbstractTokenCacheProvider.cs +++ b/src/Microsoft.Identity.Web.TokenCache/MsalAbstractTokenCacheProvider.cs @@ -101,7 +101,7 @@ private byte[] ProtectBytes(byte[] msalBytes) } private static CacheSerializerHints CreateHintsFromArgs(TokenCacheNotificationArgs args) => new CacheSerializerHints - { CancellationToken = args.CancellationToken, SuggestedCacheExpiry = args.SuggestedCacheExpiry }; + { CancellationToken = args.CancellationToken, SuggestedCacheExpiry = args.SuggestedCacheExpiry, TelemetryData = args.TelemetryData }; private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args) { diff --git a/src/Microsoft.Identity.Web.TokenCache/Properties/InternalsVisibleTo.cs b/src/Microsoft.Identity.Web.TokenCache/Properties/InternalsVisibleTo.cs index bdaa961fd..41022c2b1 100644 --- a/src/Microsoft.Identity.Web.TokenCache/Properties/InternalsVisibleTo.cs +++ b/src/Microsoft.Identity.Web.TokenCache/Properties/InternalsVisibleTo.cs @@ -5,3 +5,4 @@ // Allow this assembly to be serviced when run on desktop CLR [assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] +[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Common, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")] diff --git a/src/Microsoft.Identity.Web.TokenCache/TokenCacheExtensions.cs b/src/Microsoft.Identity.Web.TokenCache/TokenCacheExtensions.cs index 0289b1661..1b210c38e 100644 --- a/src/Microsoft.Identity.Web.TokenCache/TokenCacheExtensions.cs +++ b/src/Microsoft.Identity.Web.TokenCache/TokenCacheExtensions.cs @@ -19,7 +19,8 @@ namespace Microsoft.Identity.Web /// public static class TokenCacheExtensions { - private static readonly ConcurrentDictionary s_serviceProviderFromAction + //internal for testing only + internal static readonly ConcurrentDictionary s_serviceProviderFromAction = new ConcurrentDictionary(); /// diff --git a/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpMessageHandler.cs b/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpMessageHandler.cs index b9d7d292e..ff1984057 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpMessageHandler.cs +++ b/tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpMessageHandler.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -11,6 +12,8 @@ namespace Microsoft.Identity.Web.Test.Common.Mocks { public class MockHttpMessageHandler : HttpMessageHandler { + public Func ReplaceMockHttpMessageHandler; + #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public MockHttpMessageHandler() #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. @@ -41,6 +44,25 @@ protected override Task SendAsync(HttpRequestMessage reques var uri = request.RequestUri; Assert.NotNull(uri); + + //Intercept instance discovery requests and serve a response. + //Also, requeue the current mock handler for MSAL's next request. +#if NET6_0_OR_GREATER + if (uri.AbsoluteUri.Contains("/discovery/instance", StringComparison.OrdinalIgnoreCase)) +#else + if (uri.AbsoluteUri.Contains("/discovery/instance")) +#endif + { + ReplaceMockHttpMessageHandler(this); + + var responseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(TestConstants.DiscoveryJsonResponse), + }; + + return Task.FromResult(responseMessage); + } + if (!string.IsNullOrEmpty(ExpectedUrl)) { Assert.Equal( @@ -59,7 +81,7 @@ protected override Task SendAsync(HttpRequestMessage reques string postData = request.Content.ReadAsStringAsync().Result; } - return new TaskFactory().StartNew(() => ResponseMessage, cancellationToken); + return Task.FromResult(ResponseMessage); } } } diff --git a/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/TestMsalDistributedTokenCacheAdapter.cs b/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/TestMsalDistributedTokenCacheAdapter.cs index 18ae3b83e..7b51eeafa 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/TestMsalDistributedTokenCacheAdapter.cs +++ b/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/TestMsalDistributedTokenCacheAdapter.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Identity.Client.TelemetryCore.TelemetryClient; using Microsoft.Identity.Web.TokenCacheProviders; using Microsoft.Identity.Web.TokenCacheProviders.Distributed; @@ -42,5 +43,10 @@ public async Task TestWriteCacheBytesAsync(string cacheKey, byte[] bytes, CacheS { return await ReadCacheBytesAsync(cacheKey).ConfigureAwait(false); } + + public async Task TestReadCacheBytesAsync(string cacheKey, TelemetryData telemetryData) + { + return await ReadCacheBytesAsync(cacheKey, new CacheSerializerHints() { TelemetryData = telemetryData }).ConfigureAwait(false); + } } } diff --git a/tests/Microsoft.Identity.Web.Test/CacheExtensionsTests.cs b/tests/Microsoft.Identity.Web.Test/CacheExtensionsTests.cs index 5ce5b9f7c..78986cf5f 100644 --- a/tests/Microsoft.Identity.Web.Test/CacheExtensionsTests.cs +++ b/tests/Microsoft.Identity.Web.Test/CacheExtensionsTests.cs @@ -2,13 +2,17 @@ // Licensed under the MIT License. using System; +using System.Globalization; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Cache; using Microsoft.Identity.Web.Test.Common; using Microsoft.Identity.Web.Test.Common.Mocks; +using Microsoft.Identity.Web.TokenCacheProviders.Distributed; +using Microsoft.IdentityModel.Abstractions; using Xunit; namespace Microsoft.Identity.Web.Test @@ -29,19 +33,42 @@ public void InMemoryCacheExtensionsTests() public async Task CacheExtensions_CcaAlreadyExists_TestsAsync() { AuthenticationResult result; + TestTelemetryClient testTelemetryClient = new TestTelemetryClient(TestConstants.ClientId); // new InMemory serializer and new cca - result = await CreateAppAndGetTokenAsync(CacheType.InMemory, addInstanceMock: true).ConfigureAwait(false); + result = await CreateAppAndGetTokenAsync(CacheType.InMemory, testTelemetryClient).ConfigureAwait(false); Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); + AssertCacheTelemetry(testTelemetryClient, CacheLevel.None); - result = await CreateAppAndGetTokenAsync(CacheType.InMemory, addTokenMock: false).ConfigureAwait(false); + result = await CreateAppAndGetTokenAsync(CacheType.InMemory, testTelemetryClient, addTokenMock: false).ConfigureAwait(false); Assert.Equal(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); + AssertCacheTelemetry(testTelemetryClient, CacheLevel.L1Cache); + + //Resetting token caches due to potential collision with other tests + TokenCacheExtensions.s_serviceProviderFromAction.Clear(); // new DistributedInMemory and same cca - result = await CreateAppAndGetTokenAsync(CacheType.DistributedInMemory).ConfigureAwait(false); + result = await CreateAppAndGetTokenAsync(CacheType.DistributedInMemory, testTelemetryClient).ConfigureAwait(false); + Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); + AssertCacheTelemetry(testTelemetryClient, CacheLevel.None); + + result = await CreateAppAndGetTokenAsync(CacheType.DistributedInMemory, testTelemetryClient, addTokenMock: false).ConfigureAwait(false); + Assert.Equal(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); + AssertCacheTelemetry(testTelemetryClient, CacheLevel.L1Cache); + } + + [Fact] + public async Task CacheExtensions_CcaAlreadyExistsL2_TestsAsync() + { + AuthenticationResult result; + TestTelemetryClient testTelemetryClient = new TestTelemetryClient(TestConstants.ClientId); + // new DistributedInMemory serializer with L1 cache disabled + result = await CreateAppAndGetTokenAsync(CacheType.DistributedInMemory, testTelemetryClient, disableL1Cache: true).ConfigureAwait(false); Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); + AssertCacheTelemetry(testTelemetryClient, CacheLevel.None); - result = await CreateAppAndGetTokenAsync(CacheType.DistributedInMemory, addTokenMock: false).ConfigureAwait(false); + result = await CreateAppAndGetTokenAsync(CacheType.DistributedInMemory, testTelemetryClient, addTokenMock: false, disableL1Cache: true).ConfigureAwait(false); Assert.Equal(TokenSource.Cache, result.AuthenticationResultMetadata.TokenSource); + AssertCacheTelemetry(testTelemetryClient, CacheLevel.L2Cache); } [Fact] @@ -68,7 +95,7 @@ public void InMemoryCache_WithServices_ExtensionsTests() [Fact] public void InMemoryCache_WithServices_NoCca_ThrowsException_Tests() - { + { IConfidentialClientApplication confidentialApp = null!; var ex = Assert.Throws(() => confidentialApp.AddInMemoryTokenCache(services => { @@ -102,7 +129,7 @@ public void DistributedCacheExtensionsTests() [Fact] public void DistributedCacheExtensions_NoCca_ThrowsException_Tests() - { + { IConfidentialClientApplication confidentialApp = null!; var ex = Assert.Throws(() => confidentialApp.AddDistributedTokenCache(services => { @@ -119,49 +146,52 @@ public void DistributedCacheExtensions_NoService_ThrowsException_Tests() var ex = Assert.Throws(() => confidentialApp.AddDistributedTokenCache(null!)); Assert.Equal("initializeDistributedCache", ex.ParamName); - } - + } + [Fact] - public async Task SingletonMsal_ResultsInCorrectCacheEntries_Test() - { - var tenantId1 = "tenant1"; - var tenantId2 = "tenant2"; - var cacheKey1 = $"{TestConstants.ClientId}_{tenantId1}_AppTokenCache"; - var cacheKey2 = $"{TestConstants.ClientId}_{tenantId2}_AppTokenCache"; - + public async Task SingletonMsal_ResultsInCorrectCacheEntries_Test() + { + var tenantId1 = "tenant1"; + var tenantId2 = "tenant2"; + var cacheKey1 = $"{TestConstants.ClientId}_{tenantId1}_AppTokenCache"; + var cacheKey2 = $"{TestConstants.ClientId}_{tenantId2}_AppTokenCache"; + + //Resetting token caches due to potential collision with other tests + TokenCacheExtensions.s_serviceProviderFromAction.Clear(); + using MockHttpClientFactory mockHttpClient = new MockHttpClientFactory(); using (mockHttpClient.AddMockHandler(MockHttpCreator.CreateClientCredentialTokenHandler())) - using (mockHttpClient.AddMockHandler(MockHttpCreator.CreateClientCredentialTokenHandler())) - { - var confidentialApp = ConfidentialClientApplicationBuilder - .Create(TestConstants.ClientId) - .WithAuthority(TestConstants.AuthorityCommonTenant) - .WithHttpClientFactory(mockHttpClient) - .WithInstanceDiscovery(false) - .WithClientSecret(TestConstants.ClientSecret) - .Build(); - - var distributedCache = new TestDistributedCache(); - confidentialApp.AddDistributedTokenCache(services => - { - services.AddSingleton(distributedCache); - }); - - // Different tenants used to created different cache entries - var result1 = await confidentialApp.AcquireTokenForClient(new[] { TestConstants.s_scopeForApp }) - .WithTenantId(tenantId1) - .ExecuteAsync().ConfigureAwait(false); - var result2 = await confidentialApp.AcquireTokenForClient(new[] { TestConstants.s_scopeForApp }) - .WithTenantId(tenantId2) - .ExecuteAsync().ConfigureAwait(false); - - Assert.Equal(TokenSource.IdentityProvider, result1.AuthenticationResultMetadata.TokenSource); - Assert.Equal(TokenSource.IdentityProvider, result2.AuthenticationResultMetadata.TokenSource); - Assert.Equal(2, distributedCache._dict.Count); - Assert.Equal(distributedCache.Get(cacheKey1)!.Length, distributedCache.Get(cacheKey2)!.Length); - } - } - + using (mockHttpClient.AddMockHandler(MockHttpCreator.CreateClientCredentialTokenHandler())) + { + var confidentialApp = ConfidentialClientApplicationBuilder + .Create(TestConstants.ClientId) + .WithAuthority(TestConstants.AuthorityCommonTenant) + .WithHttpClientFactory(mockHttpClient) + .WithInstanceDiscovery(false) + .WithClientSecret(TestConstants.ClientSecret) + .Build(); + + var distributedCache = new TestDistributedCache(); + confidentialApp.AddDistributedTokenCache(services => + { + services.AddSingleton(distributedCache); + }); + + // Different tenants used to created different cache entries + var result1 = await confidentialApp.AcquireTokenForClient(new[] { TestConstants.s_scopeForApp }) + .WithTenantId(tenantId1) + .ExecuteAsync().ConfigureAwait(false); + var result2 = await confidentialApp.AcquireTokenForClient(new[] { TestConstants.s_scopeForApp }) + .WithTenantId(tenantId2) + .ExecuteAsync().ConfigureAwait(false); + + Assert.Equal(TokenSource.IdentityProvider, result1.AuthenticationResultMetadata.TokenSource); + Assert.Equal(TokenSource.IdentityProvider, result2.AuthenticationResultMetadata.TokenSource); + Assert.Equal(2, distributedCache._dict.Count); + Assert.Equal(distributedCache.Get(cacheKey1)!.Length, distributedCache.Get(cacheKey2)!.Length); + } + } + private enum CacheType { InMemory, @@ -170,21 +200,20 @@ private enum CacheType private static async Task CreateAppAndGetTokenAsync( CacheType cacheType, + ITelemetryClient telemetryClient, bool addTokenMock = true, - bool addInstanceMock = false) + bool disableL1Cache = false) { using MockHttpClientFactory mockHttp = new MockHttpClientFactory(); - using var discoveryHandler = MockHttpCreator.CreateInstanceDiscoveryMockHandler(); using var tokenHandler = MockHttpCreator.CreateClientCredentialTokenHandler(); - if (addInstanceMock) - { - mockHttp.AddMockHandler(discoveryHandler); - } // for when the token is requested from ESTS if (addTokenMock) { mockHttp.AddMockHandler(tokenHandler); + + //Enables the mock handler to requeue requests that have been intercepted for instance discovery for example + tokenHandler.ReplaceMockHttpMessageHandler = mockHttp.AddMockHandler; } var confidentialApp = ConfidentialClientApplicationBuilder @@ -192,6 +221,7 @@ private static async Task CreateAppAndGetTokenAsync( .WithAuthority(TestConstants.AuthorityCommonTenant) .WithHttpClientFactory(mockHttp) .WithClientSecret(TestConstants.ClientSecret) + .WithTelemetryClient(telemetryClient) .Build(); switch (cacheType) @@ -206,6 +236,14 @@ private static async Task CreateAppAndGetTokenAsync( services.AddDistributedMemoryCache(); services.AddLogging(configure => configure.AddConsole()) .Configure(options => options.MinLevel = Microsoft.Extensions.Logging.LogLevel.Warning); + + if (disableL1Cache) + { + services.Configure(options => + { + options.DisableL1Cache = true; + }); + } }); break; } @@ -213,10 +251,17 @@ private static async Task CreateAppAndGetTokenAsync( var result = await confidentialApp.AcquireTokenForClient(new[] { TestConstants.s_scopeForApp }) .ExecuteAsync().ConfigureAwait(false); + tokenHandler.ReplaceMockHttpMessageHandler = null!; return result; } - private IConfidentialClientApplication CreateCca() => + private void AssertCacheTelemetry(TestTelemetryClient testTelemetryClient, CacheLevel cacheLevel) + { + TelemetryEventDetails eventDetails = testTelemetryClient.TestTelemetryEventDetails; + Assert.Equal(Convert.ToInt64(cacheLevel, new CultureInfo("en-US")), eventDetails.Properties["CacheLevel"]); + } + + private IConfidentialClientApplication CreateCca() => ConfidentialClientApplicationBuilder .Create(TestConstants.ClientId) .WithAuthority(TestConstants.AuthorityCommonTenant) diff --git a/tests/Microsoft.Identity.Web.Test/L1L2CacheTests.cs b/tests/Microsoft.Identity.Web.Test/L1L2CacheTests.cs index df3ca637d..8857f3d38 100644 --- a/tests/Microsoft.Identity.Web.Test/L1L2CacheTests.cs +++ b/tests/Microsoft.Identity.Web.Test/L1L2CacheTests.cs @@ -7,6 +7,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Identity.Client.Cache; +using Microsoft.Identity.Client.TelemetryCore.TelemetryClient; using Microsoft.Identity.Web.Test.Common.TestHelpers; using Microsoft.Identity.Web.TokenCacheProviders; using Microsoft.Identity.Web.TokenCacheProviders.Distributed; @@ -181,6 +183,7 @@ public async Task SetL1Cache_ReadL1_TestAsync() // Arrange byte[] cache = new byte[3]; cache[0] = 4; + TelemetryData telemetryData = new TelemetryData(); AssertCacheValues(_testCacheAdapter); Assert.NotNull(_testCacheAdapter._memoryCache); Assert.Equal(0, _testCacheAdapter._memoryCache.Count); @@ -189,11 +192,12 @@ public async Task SetL1Cache_ReadL1_TestAsync() Assert.Empty(L2Cache._dict); // Act - byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey).ConfigureAwait(false); + byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey, telemetryData).ConfigureAwait(false); // Assert Assert.NotNull(result); Assert.Equal(4, result[0]); + Assert.Equal(CacheLevel.L1Cache, telemetryData.CacheLevel); } [Fact] @@ -202,6 +206,7 @@ public async Task EmptyL1Cache_ReadL2AndSetL1_TestAsync() // Arrange byte[] cache = new byte[3]; cache[0] = 4; + TelemetryData telemetryData = new TelemetryData(); AssertCacheValues(_testCacheAdapter); _testCacheAdapter._distributedCache.Set(DefaultCacheKey, cache); Assert.Single(L2Cache._dict); @@ -209,13 +214,48 @@ public async Task EmptyL1Cache_ReadL2AndSetL1_TestAsync() Assert.Equal(0, _testCacheAdapter._memoryCache.Count); // Act - byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey).ConfigureAwait(false); + byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey, telemetryData).ConfigureAwait(false); // Assert Assert.NotNull(result); Assert.Equal(4, result[0]); Assert.Equal(1, _testCacheAdapter._memoryCache.Count); Assert.Single(L2Cache._dict); + Assert.Equal(CacheLevel.L2Cache, telemetryData.CacheLevel); + } + + [Fact] + public async Task EmptyL1Cache_ReadL2AndSetL1_ForTelemetryTestAsync() + { + // Arrange + byte[] cache = new byte[3]; + cache[0] = 4; + AssertCacheValues(_testCacheAdapter); + _testCacheAdapter._distributedCache.Set(DefaultCacheKey, cache); + TelemetryData telemetryData = new TelemetryData(); + Assert.Single(L2Cache._dict); + Assert.NotNull(_testCacheAdapter._memoryCache); + Assert.Equal(0, _testCacheAdapter._memoryCache.Count); + + // Act + byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey, telemetryData).ConfigureAwait(false); + + // Assert + Assert.NotNull(result); + Assert.Equal(4, result[0]); + Assert.Equal(1, _testCacheAdapter._memoryCache.Count); + Assert.Single(L2Cache._dict); + Assert.Equal(CacheLevel.L2Cache, telemetryData.CacheLevel); + + // Act + result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey, telemetryData).ConfigureAwait(false); + + // Assert + Assert.NotNull(result); + Assert.Equal(4, result[0]); + Assert.Equal(1, _testCacheAdapter._memoryCache.Count); + Assert.Single(L2Cache._dict); + Assert.Equal(CacheLevel.L1Cache, telemetryData.CacheLevel); } [Fact] @@ -224,17 +264,19 @@ public async Task EmptyL1L2Cache_ReturnNullCacheResult_TestAsync() // Arrange byte[] cache = new byte[3]; cache[0] = 4; + TelemetryData telemetryData = new TelemetryData(); AssertCacheValues(_testCacheAdapter); Assert.NotNull(_testCacheAdapter._memoryCache); Assert.Equal(0, _testCacheAdapter._memoryCache.Count); // Act - byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey).ConfigureAwait(false); + byte[]? result = await _testCacheAdapter.TestReadCacheBytesAsync(DefaultCacheKey, telemetryData).ConfigureAwait(false); // Assert Assert.Null(result); Assert.Equal(0, _testCacheAdapter._memoryCache.Count); Assert.Empty(L2Cache._dict); + Assert.Equal(CacheLevel.None, telemetryData.CacheLevel); } [Fact] diff --git a/tests/Microsoft.Identity.Web.Test/Microsoft.Identity.Web.Test.csproj b/tests/Microsoft.Identity.Web.Test/Microsoft.Identity.Web.Test.csproj index f904618b0..cb7d90629 100644 --- a/tests/Microsoft.Identity.Web.Test/Microsoft.Identity.Web.Test.csproj +++ b/tests/Microsoft.Identity.Web.Test/Microsoft.Identity.Web.Test.csproj @@ -43,6 +43,7 @@ + diff --git a/tests/Microsoft.Identity.Web.Test/TestTelemetryClient.cs b/tests/Microsoft.Identity.Web.Test/TestTelemetryClient.cs new file mode 100644 index 000000000..37fa1e687 --- /dev/null +++ b/tests/Microsoft.Identity.Web.Test/TestTelemetryClient.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Abstractions; + +namespace Microsoft.Identity.Web.Test +{ + internal class TestTelemetryClient : ITelemetryClient + { + private const string _eventName = "acquire_token"; + public TelemetryEventDetails TestTelemetryEventDetails { get; set; } + + public TestTelemetryClient(string clientId) + { + ClientId = clientId; + TestTelemetryEventDetails = new MsalTelemetryEventDetails(_eventName); + } + + public string ClientId { get; set; } + + public void Initialize() + { + + } + + public bool IsEnabled() + { + return true; + } + + public bool IsEnabled(string eventName) + { + return _eventName.Equals(eventName, StringComparison.Ordinal); + } + + public void TrackEvent(TelemetryEventDetails eventDetails) + { + TestTelemetryEventDetails = eventDetails; + } + + public void TrackEvent(string eventName, IDictionary stringProperties, IDictionary longProperties, IDictionary boolProperties, IDictionary dateTimeProperties, IDictionary doubleProperties, IDictionary guidProperties) + { + throw new NotImplementedException(); + } + } + + internal class MsalTelemetryEventDetails : TelemetryEventDetails + { + public MsalTelemetryEventDetails(string eventName) + { + Name = eventName; + } + } +}