1
0
mirror of https://github.com/bitwarden/server.git synced 2024-12-30 18:17:36 +01:00
bitwarden-server/test/Common/Helpers/AssertHelper.cs
Rui Tomé 5ecf7b9440
[EC-394] SCIM server integration tests (#2197)
* [EC-394] Added ScimApplicationFactory to handle tests for Scim controllers

* [EC-394] Added Scim.IntegrationTest project with GroupsControllerTests

* [EC-394] Fixed getting Guid Id from Operation Path

* [EC-394] Added tests for GroupsController Patch action

* [EC-394] Moved tests mock data setup to ScimApplicationFactory

* [EC-394] Updated IntegrationTestCommon packages.lock.json

* [EC-394] Updated ScimApplicationFactory and GroupsControllerTests; Added UsersController Tests

* [EC-394] dotnet format

* [EC-394] Updated Identity.IntegrationTest packages.lock.json

* [EC-394] Updated Scim.IntegrationTest packages.lock.json

* [EC-394] dotnet format

* [EC-394] Reverted change on getting GUID from GetOperationPathId

* [EC-394] Fixed sending userId on Patch_AddSingleMember_Success and Patch_RemoveSingleMember_Success

* [EC-394] Updated test to send request with two operations

* [EC-394] Removed Scim dependency from IntegrationTestCommon

* [EC-394] Reverted changes to packages.lock.json.
Ran dotnet format

* [EC-394] Updated Scim.IntegrationTest packages.lock.json

* [EC-394] Updated GroupsControllerTests and UsersControllerTests to implement IAsyncLifetime to cleanup database before each test

* [EC-394] Declared variables for GetList parameters

* [EC-394] Updated AssertHelper.AssertPropertyEqual to compare each item in an IEnumerable property

* [EC-394] Updated AssertHelper.AssertPropertyEqual to check if type is comparable

* [EC-394] Removed unused variable from ScimApplicationFactory

* Apply suggestions from code review

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* [EC-394] Changed test users emails to domain example.com

* [EC-394] Restore solution file

* [EC-394] Added Scim.IntegrationTest to sln

* [EC-394] Updated integration tests to be clearer and check responses in detail

* [EC-394] Using NoopMailService to mock sending email invitations in tests

* [EC-394] Removed multiple references to the same variable ScimApplicationFactory.TestOrganizationId1

* [EC-394] Updated const variable names

* [EC-394] Using AssertPropertyEqualPredicate for IEnumerable properties

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2022-09-08 09:00:59 +01:00

237 lines
9.2 KiB
C#

using System.Collections;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Http;
using Xunit;
using Xunit.Sdk;
namespace Bit.Test.Common.Helpers;
public static class AssertHelper
{
public static void AssertPropertyEqual(object expected, object actual, params string[] excludedPropertyStrings)
{
var relevantExcludedProperties = excludedPropertyStrings.Where(name => !name.Contains('.')).ToList();
if (expected == null)
{
Assert.Null(actual);
return;
}
if (actual == null)
{
throw new Exception("Actual object is null but expected is not");
}
foreach (var expectedPropInfo in expected.GetType().GetProperties().Where(pi => !relevantExcludedProperties.Contains(pi.Name) && !pi.GetIndexParameters().Any()))
{
var actualPropInfo = actual.GetType().GetProperty(expectedPropInfo.Name);
if (actualPropInfo == null)
{
throw new Exception(string.Concat($"Expected actual object to contain a property named {expectedPropInfo.Name}, but it does not\n",
$"Expected:\n{JsonSerializer.Serialize(expected, JsonHelpers.Indented)}\n",
$"Actual:\n{JsonSerializer.Serialize(actual, JsonHelpers.Indented)}"));
}
if (typeof(IComparable).IsAssignableFrom(expectedPropInfo.PropertyType) || expectedPropInfo.PropertyType.IsPrimitive || expectedPropInfo.PropertyType.IsValueType)
{
Assert.Equal(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual));
}
else if (expectedPropInfo.PropertyType == typeof(JsonDocument) && actualPropInfo.PropertyType == typeof(JsonDocument))
{
static string JsonDocString(PropertyInfo info, object obj) => JsonSerializer.Serialize(info.GetValue(obj));
Assert.Equal(JsonDocString(expectedPropInfo, expected), JsonDocString(actualPropInfo, actual));
}
else if (typeof(IEnumerable).IsAssignableFrom(expectedPropInfo.PropertyType) && typeof(IEnumerable).IsAssignableFrom(actualPropInfo.PropertyType))
{
var expectedItems = ((IEnumerable)expectedPropInfo.GetValue(expected)).Cast<object>();
var actualItems = ((IEnumerable)actualPropInfo.GetValue(actual)).Cast<object>();
AssertPropertyEqualPredicate(expectedItems, excludedPropertyStrings)(actualItems);
}
else
{
var prefix = $"{expectedPropInfo.PropertyType.Name}.";
var nextExcludedProperties = excludedPropertyStrings.Where(name => name.StartsWith(prefix))
.Select(name => name[prefix.Length..]).ToArray();
AssertPropertyEqual(expectedPropInfo.GetValue(expected), actualPropInfo.GetValue(actual), nextExcludedProperties);
}
}
}
private static Predicate<T> AssertPropertyEqualPredicate<T>(T expected, params string[] excludedPropertyStrings) => (actual) =>
{
AssertPropertyEqual(expected, actual, excludedPropertyStrings);
return true;
};
public static Expression<Predicate<T>> AssertPropertyEqual<T>(T expected, params string[] excludedPropertyStrings) =>
(T actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
private static Predicate<IEnumerable<T>> AssertPropertyEqualPredicate<T>(IEnumerable<T> expected, params string[] excludedPropertyStrings) => (actual) =>
{
// IEnumerable.Zip doesn't account for different lengths, we need to check this ourselves
if (actual.Count() != expected.Count())
{
throw new Exception(string.Concat($"Actual IEnumerable does not have the expected length.\n",
$"Expected: {expected.Count()}\n",
$"Actual: {actual.Count()}"));
}
var elements = expected.Zip(actual);
foreach (var (expectedEl, actualEl) in elements)
{
AssertPropertyEqual(expectedEl, actualEl, excludedPropertyStrings);
}
return true;
};
public static Expression<Predicate<IEnumerable<T>>> AssertPropertyEqual<T>(IEnumerable<T> expected, params string[] excludedPropertyStrings) =>
(actual) => AssertPropertyEqualPredicate(expected, excludedPropertyStrings)(actual);
private static Predicate<T> AssertEqualExpectedPredicate<T>(T expected) => (actual) =>
{
Assert.Equal(expected, actual);
return true;
};
public static Expression<Predicate<T>> AssertEqualExpected<T>(T expected) =>
(T actual) => AssertEqualExpectedPredicate(expected)(actual);
public static JsonElement AssertJsonProperty(JsonElement element, string propertyName, JsonValueKind jsonValueKind)
{
if (!element.TryGetProperty(propertyName, out var subElement))
{
throw new XunitException($"Could not find property by name '{propertyName}'");
}
Assert.Equal(jsonValueKind, subElement.ValueKind);
return subElement;
}
public static void AssertEqualJson(JsonElement a, JsonElement b)
{
switch (a.ValueKind)
{
case JsonValueKind.Array:
Assert.Equal(JsonValueKind.Array, b.ValueKind);
AssertEqualJsonArray(a, b);
break;
case JsonValueKind.Object:
Assert.Equal(JsonValueKind.Object, b.ValueKind);
AssertEqualJsonObject(a, b);
break;
case JsonValueKind.False:
Assert.Equal(JsonValueKind.False, b.ValueKind);
break;
case JsonValueKind.True:
Assert.Equal(JsonValueKind.True, b.ValueKind);
break;
case JsonValueKind.Number:
Assert.Equal(JsonValueKind.Number, b.ValueKind);
Assert.Equal(a.GetDouble(), b.GetDouble());
break;
case JsonValueKind.String:
Assert.Equal(JsonValueKind.String, b.ValueKind);
Assert.Equal(a.GetString(), b.GetString());
break;
case JsonValueKind.Null:
Assert.Equal(JsonValueKind.Null, b.ValueKind);
break;
default:
throw new XunitException($"Bad JsonValueKind '{a.ValueKind}'");
}
}
private static void AssertEqualJsonObject(JsonElement a, JsonElement b)
{
Debug.Assert(a.ValueKind == JsonValueKind.Object && b.ValueKind == JsonValueKind.Object);
var aObjectEnumerator = a.EnumerateObject();
var bObjectEnumerator = b.EnumerateObject();
while (true)
{
var aCanMove = aObjectEnumerator.MoveNext();
var bCanMove = bObjectEnumerator.MoveNext();
if (aCanMove)
{
Assert.True(bCanMove, $"a was able to enumerate over object '{a}' but b was NOT able to '{b}'");
}
else
{
Assert.False(bCanMove, $"a was NOT able to enumerate over object '{a}' but b was able to '{b}'");
}
if (aCanMove == false && bCanMove == false)
{
// They both can't continue to enumerate at the same time, that is valid
break;
}
var aProp = aObjectEnumerator.Current;
var bProp = bObjectEnumerator.Current;
Assert.Equal(aProp.Name, bProp.Name);
// Recursion!
AssertEqualJson(aProp.Value, bProp.Value);
}
}
private static void AssertEqualJsonArray(JsonElement a, JsonElement b)
{
Debug.Assert(a.ValueKind == JsonValueKind.Array && b.ValueKind == JsonValueKind.Array);
var aArrayEnumerator = a.EnumerateArray();
var bArrayEnumerator = b.EnumerateArray();
while (true)
{
var aCanMove = aArrayEnumerator.MoveNext();
var bCanMove = bArrayEnumerator.MoveNext();
if (aCanMove)
{
Assert.True(bCanMove, $"a was able to enumerate over array '{a}' but b was NOT able to '{b}'");
}
else
{
Assert.False(bCanMove, $"a was NOT able to enumerate over array '{a}' but b was able to '{b}'");
}
if (aCanMove == false && bCanMove == false)
{
// They both can't continue to enumerate at the same time, that is valid
break;
}
var aElement = aArrayEnumerator.Current;
var bElement = bArrayEnumerator.Current;
// Recursion!
AssertEqualJson(aElement, bElement);
}
}
public async static Task<T> AssertResponseTypeIs<T>(HttpContext context)
{
return await JsonSerializer.DeserializeAsync<T>(context.Response.Body);
}
public static TimeSpan AssertRecent(DateTime dateTime, int skewSeconds = 2)
=> AssertRecent(dateTime, TimeSpan.FromSeconds(skewSeconds));
public static TimeSpan AssertRecent(DateTime dateTime, TimeSpan skew)
{
var difference = DateTime.UtcNow - dateTime;
Assert.True(difference < skew);
return difference;
}
}