Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Cache.Items;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.PlatformsCommon.Shared;
using Microsoft.Identity.Lab.Api.Helpers;

namespace Microsoft.Identity.Lab.Api.Helpers
{
internal class AppAccessorWithPartitionAsserts : InMemoryPartitionedAppTokenCacheAccessor
{
public AppAccessorWithPartitionAsserts(
ILoggerAdapter logger,
CacheOptions tokenCacheAccessorOptions) : base(logger, tokenCacheAccessorOptions)
{

}

public override List<MsalAccessTokenCacheItem> GetAllAccessTokens(string partitionKey = null, ILoggerAdapter requestlogger = null)
{
Assert.IsNotNull(partitionKey);
return base.GetAllAccessTokens(partitionKey, requestlogger);
}

public override List<MsalAccountCacheItem> GetAllAccounts(string partitionKey = null, ILoggerAdapter requestlogger = null)
{
Assert.IsNotNull(partitionKey);
Assert.Fail("App token cache - do not call GetAllAccounts");
throw new InvalidOperationException();
}

public override List<MsalIdTokenCacheItem> GetAllIdTokens(string partitionKey = null, ILoggerAdapter requestlogger = null)
{
Assert.IsNotNull(partitionKey);

Assert.Fail("App token cache - do not call GetAllIdTokens");
throw new InvalidOperationException();
}

public override List<MsalRefreshTokenCacheItem> GetAllRefreshTokens(string partitionKey = null, ILoggerAdapter requestlogger = null)
{
Assert.IsNotNull(partitionKey);

Assert.Fail("App token cache - do not call GetAllRefreshTokens");
throw new InvalidOperationException();
}

public override bool HasAccessOrRefreshTokens()
{
Assert.Fail("HasAccessOrRefreshTokens was called. It should not be called unless the token cache serialization hooks");
Comment thread
gladjohn marked this conversation as resolved.
throw new InvalidOperationException();
}
}
}
95 changes: 95 additions & 0 deletions src/client/Microsoft.Identity.Lab.Api/Helpers/Assert.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Identity.Lab.Api.Helpers
{
/// <summary>
/// Lightweight assertion helper that throws <see cref="InvalidOperationException"/>
/// instead of depending on MSTest. Intended for use in mock infrastructure only.
/// </summary>
internal static class Assert
{
public static void AreEqual<T>(T expected, T actual, string message = null)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
{
throw new InvalidOperationException(
message ?? $"Assert.AreEqual failed. Expected: <{expected}>. Actual: <{actual}>.");
}
}

public static void IsTrue(bool condition, string message = null)
{
if (!condition)
{
throw new InvalidOperationException(
message ?? "Assert.IsTrue failed.");
}
}

public static void IsFalse(bool condition, string message = null)
{
if (condition)
{
throw new InvalidOperationException(
message ?? "Assert.IsFalse failed.");
}
}

public static void IsNotNull(object value, string message = null)
{
if (value == null)
{
throw new InvalidOperationException(
message ?? "Assert.IsNotNull failed.");
}
}

public static void IsEmpty<T>(ICollection<T> collection, string message = null)
{
if (collection != null && collection.Count > 0)
{
throw new InvalidOperationException(
message ?? $"Assert.IsEmpty failed. Collection has {collection.Count} element(s).");
}
}

public static void IsEmpty<T>(IEnumerable<T> collection, string message = null)
{
if (collection != null && collection.Any())
{
throw new InvalidOperationException(
Comment thread
gladjohn marked this conversation as resolved.
message ?? "Assert.IsEmpty failed. Collection is not empty.");
}
}

public static void HasCount<T>(int expectedCount, ICollection<T> collection, string message = null)
{
int actual = collection?.Count ?? 0;
if (actual != expectedCount)
{
throw new InvalidOperationException(
message ?? $"Assert.HasCount failed. Expected: {expectedCount}. Actual: {actual}.");
}
}
Comment thread
gladjohn marked this conversation as resolved.

public static void Contains<T>(T item, ICollection<T> collection, string message = null)
{
if (collection == null || !collection.Contains(item))
{
throw new InvalidOperationException(
message ?? $"Assert.Contains failed. Item <{item}> not found in collection.");
}
}

public static void Fail(string message)
{
throw new InvalidOperationException(message ?? "Assert.Fail was called.");
}
}
}
142 changes: 142 additions & 0 deletions src/client/Microsoft.Identity.Lab.Api/Helpers/CoreAssert.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Lab.Api.Helpers;

namespace Microsoft.Identity.Lab.Api.Helpers
{
/// <summary>
/// Provides custom assertion helpers for MSAL.NET test scenarios.
/// </summary>
internal static class CoreAssert
{
/// <summary>
/// Asserts that two scope strings represent the same set of scopes.
/// </summary>
public static void AreScopesEqual(string scopesExpected, string scopesActual)
{
var expectedScopes = ScopeHelper.ConvertStringToScopeSet(scopesExpected);
var actualScopes = ScopeHelper.ConvertStringToScopeSet(scopesActual);

Assert.HasCount(expectedScopes.Count, actualScopes);
foreach (string expectedScope in expectedScopes)
{
Assert.Contains(expectedScope, actualScopes);
}
}

internal static void AreAccountsEqual(
string expectedUsername,
string expectedEnv,
string expectedId,
string expectedTid,
string expectedOid,
params IAccount[] accounts)
{
foreach (var account in accounts)
{
Assert.AreEqual(expectedUsername, account.Username);
Assert.AreEqual(expectedEnv, account.Environment);
Assert.AreEqual(expectedId, account.HomeAccountId.Identifier);
Assert.AreEqual(expectedTid, account.HomeAccountId.TenantId);
Assert.AreEqual(expectedOid, account.HomeAccountId.ObjectId);
}
}

/// <summary>
/// Asserts that three values are all equal to each other.
/// </summary>
public static void AreEqual<T>(T val1, T val2, T val3)
{
Assert.AreEqual(val1, val2, "First and second values differ");
Assert.AreEqual(val1, val3, "First and third values differ");
}

/// <summary>
/// Asserts that two <see cref="DateTimeOffset"/> values are within one second of each other.
/// </summary>
public static void AreWithinOneSecond(DateTimeOffset expected, DateTimeOffset actual, string message = "")
{
IsWithinRange(expected, actual, TimeSpan.FromSeconds(1), message);
}

/// <summary>
/// Asserts that two <see cref="DateTimeOffset"/> values are within a specified range of each other.
/// </summary>
public static void IsWithinRange(DateTimeOffset expected, DateTimeOffset actual, TimeSpan range, string message = "")
{
TimeSpan t = expected - actual;
Assert.IsTrue(t >= -range && t <= range,
$"{message} The dates are off by {t.TotalMilliseconds}ms, which is more than the expected {range.TotalMilliseconds}ms");
}

/// <summary>
/// Asserts that two dictionaries are equal.
/// </summary>
public static void AssertDictionariesAreEqual<TKey, TValue>(
IDictionary<TKey, TValue> dict1,
IDictionary<TKey, TValue> dict2,
IEqualityComparer<TValue> valueComparer)
{
Assert.IsTrue(DictionariesAreEqual(dict1, dict2, valueComparer));
}

/// <summary>
/// Asserts that the specified type is immutable.
/// </summary>
public static void IsImmutable<T>()
{
Assert.IsTrue(IsImmutable(typeof(T)));
}

private static bool IsImmutable(Type type)
{
if (type == typeof(string) || type.IsPrimitive || type.IsEnum)
{
return true;
}

#pragma warning disable IL2070, IL2067
var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
#pragma warning restore IL2070, IL2067
var isShallowImmutable = fieldInfos.All(f => f.IsInitOnly);

if (!isShallowImmutable)
{
return false;
}

return fieldInfos.All(f => IsImmutable(f.FieldType));
}

private static bool DictionariesAreEqual<TKey, TValue>(
IDictionary<TKey, TValue> dict1,
IDictionary<TKey, TValue> dict2,
IEqualityComparer<TValue> valueComparer)
{
if (dict1 == dict2)
return true;
if ((dict1 == null) || (dict2 == null))
return false;
if (dict1.Count != dict2.Count)
return false;

foreach (var kvp in dict1)
{
if (!dict2.TryGetValue(kvp.Key, out TValue value2))
return false;
if (!valueComparer.Equals(kvp.Value, value2))
return false;
}
return true;
}
}
}
Loading
Loading