Skip to content

Commit 6c5fd43

Browse files
committed
Add a test around TokenCache GetCacheKey customization
1 parent a153c32 commit 6c5fd43

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

tests/Microsoft.Identity.Web.Test/CacheExtensionsTests.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
using System.Globalization;
66
using System.Threading.Tasks;
77
using Microsoft.Extensions.Caching.Distributed;
8+
using Microsoft.Extensions.Caching.Memory;
89
using Microsoft.Extensions.DependencyInjection;
910
using Microsoft.Extensions.Logging;
11+
using Microsoft.Extensions.Options;
1012
using Microsoft.Identity.Client;
1113
using Microsoft.Identity.Client.Cache;
1214
using Microsoft.Identity.Web.Test.Common;
1315
using Microsoft.Identity.Web.Test.Common.Mocks;
1416
using Microsoft.Identity.Web.TokenCacheProviders.Distributed;
17+
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
1518
using Microsoft.IdentityModel.Abstractions;
1619
using Xunit;
1720

@@ -190,6 +193,111 @@ public async Task SingletonMsal_ResultsInCorrectCacheEntries_Test()
190193
}
191194
}
192195

196+
#region CacheKeyExtensibility test
197+
private const int TokenCacheMemoryLimitInMb = 100;
198+
private static MemoryCache s_memoryCache = InitiatlizeMemoryCache();
199+
200+
private static MemoryCache InitiatlizeMemoryCache()
201+
{
202+
// For 100 MB limit ... ~2KB per token entry means 50,000 entries
203+
var options = Options.Create(new MemoryCacheOptions() { SizeLimit = (TokenCacheMemoryLimitInMb / 2) * 1000 });
204+
var cache = new MemoryCache(options);
205+
206+
return cache;
207+
}
208+
209+
/// <summary>
210+
/// Token cache for MSAL based on MemoryCache, which can be partitioned by an additional key.
211+
/// For app tokens, the default key is ClientID + TenantID (and MSAL also looks for resource).
212+
/// </summary>
213+
private class PartitionedMsalTokenMemoryCacheProvider : MsalMemoryTokenCacheProvider
214+
{
215+
private readonly string? _cacheKeySuffix;
216+
217+
/// <summary>
218+
/// Ctor
219+
/// </summary>
220+
/// <param name="memoryCache">A memory cache which can be configured for max size etc.</param>
221+
/// <param name="cacheOptions">Additional cache options, which canbe ignored for app tokens.</param>
222+
/// <param name="cachePartition">An aditional partition key. If let null, the original cache scoping is used (clientID, tenantID). MSAL also looks for resource.</param>
223+
public PartitionedMsalTokenMemoryCacheProvider(
224+
IMemoryCache memoryCache,
225+
IOptions<MsalMemoryTokenCacheOptions> cacheOptions,
226+
string? cachePartition) : base(memoryCache, cacheOptions)
227+
{
228+
_cacheKeySuffix = cachePartition;
229+
}
230+
231+
public override string GetSuggestedCacheKey(TokenCacheNotificationArgs args)
232+
{
233+
return base.GetSuggestedCacheKey(args) + (_cacheKeySuffix ?? "");
234+
}
235+
}
236+
237+
private async Task<AuthenticationResult> GetTokensAssociatedWithKey(string? cachePartition, bool expectCacheHit)
238+
{
239+
MockHttpMessageHandler? handler = null;
240+
MockHttpClientFactory? mockHttpClient = null;
241+
try
242+
{
243+
244+
if (expectCacheHit == false)
245+
{
246+
mockHttpClient = new MockHttpClientFactory();
247+
handler = mockHttpClient.AddMockHandler(MockHttpCreator.CreateClientCredentialTokenHandler());
248+
}
249+
250+
var msalMemoryTokenCacheProvider =
251+
new PartitionedMsalTokenMemoryCacheProvider(
252+
s_memoryCache,
253+
Options.Create(new MsalMemoryTokenCacheOptions()),
254+
cachePartition: cachePartition);
255+
256+
var confidentialApp = ConfidentialClientApplicationBuilder
257+
.Create(TestConstants.ClientId)
258+
.WithAuthority(TestConstants.AuthorityCommonTenant)
259+
.WithHttpClientFactory(mockHttpClient)
260+
.WithInstanceDiscovery(false)
261+
.WithClientSecret(TestConstants.ClientSecret)
262+
.Build();
263+
264+
await msalMemoryTokenCacheProvider.InitializeAsync(confidentialApp.AppTokenCache).ConfigureAwait(false);
265+
266+
AuthenticationResult result = await confidentialApp
267+
.AcquireTokenForClient(["https://graph.microsoft.com/.default"])
268+
.ExecuteAsync()
269+
.ConfigureAwait(false);
270+
271+
Assert.Equal(
272+
expectCacheHit ?
273+
TokenSource.Cache :
274+
TokenSource.IdentityProvider,
275+
result.AuthenticationResultMetadata.TokenSource);
276+
277+
return result;
278+
279+
}
280+
finally
281+
{
282+
handler?.Dispose();
283+
mockHttpClient?.Dispose();
284+
}
285+
}
286+
287+
#endregion
288+
289+
[Fact]
290+
public async Task CacheKeyExtensibility()
291+
{
292+
var result = await GetTokensAssociatedWithKey("foo", expectCacheHit: false).ConfigureAwait(false);
293+
result = await GetTokensAssociatedWithKey("bar", expectCacheHit: false).ConfigureAwait(false);
294+
result = await GetTokensAssociatedWithKey(null, expectCacheHit: false).ConfigureAwait(false);
295+
296+
result = await GetTokensAssociatedWithKey("foo", expectCacheHit: true).ConfigureAwait(false);
297+
result = await GetTokensAssociatedWithKey("bar", expectCacheHit: true).ConfigureAwait(false);
298+
result = await GetTokensAssociatedWithKey(null, expectCacheHit: true).ConfigureAwait(false);
299+
}
300+
193301
private enum CacheType
194302
{
195303
InMemory,

0 commit comments

Comments
 (0)