// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Xunit;
namespace Microsoft.AspNetCore.DataProtection
{
///
/// Helpful XML-based assertions.
///
public static class XmlAssert
{
public static readonly IEqualityComparer EqualityComparer = new CallbackBasedEqualityComparer(Core.AreEqual);
///
/// Asserts that a and an are semantically equivalent.
///
public static void Equal(string expected, XElement actual)
{
Assert.NotNull(expected);
Assert.NotNull(actual);
Equal(XElement.Parse(expected), actual);
}
///
/// Asserts that two instances are semantically equivalent.
///
public static void Equal(XElement expected, XElement actual)
{
Assert.NotNull(expected);
Assert.NotNull(actual);
if (!Core.AreEqual(expected, actual))
{
Assert.True(false,
"Expected element:" + Environment.NewLine
+ expected.ToString() + Environment.NewLine
+ "Actual element:" + Environment.NewLine
+ actual.ToString());
}
}
private static class Core
{
private static readonly IEqualityComparer AttributeEqualityComparer = new CallbackBasedEqualityComparer(AreEqual);
private static bool AreEqual(XElement expected, XElement actual)
{
return expected.Name == actual.Name
&& AreEqual(expected.Attributes(), actual.Attributes())
&& AreEqual(expected.Nodes(), actual.Nodes());
}
private static bool AreEqual(IEnumerable expected, IEnumerable actual)
{
List filteredExpected = expected.Where(ShouldIncludeNodeDuringComparison).ToList();
List filteredActual = actual.Where(ShouldIncludeNodeDuringComparison).ToList();
return filteredExpected.SequenceEqual(filteredActual, EqualityComparer);
}
internal static bool AreEqual(XNode expected, XNode actual)
{
if (expected is XText && actual is XText)
{
return AreEqual((XText)expected, (XText)actual);
}
else if (expected is XElement && actual is XElement)
{
return AreEqual((XElement)expected, (XElement)actual);
}
else
{
return false;
}
}
private static bool AreEqual(XText expected, XText actual)
{
return expected.Value == actual.Value;
}
private static bool AreEqual(IEnumerable expected, IEnumerable actual)
{
List orderedExpected = expected
.Where(ShouldIncludeAttributeDuringComparison)
.OrderBy(attr => attr.Name.ToString())
.ToList();
List orderedActual = actual
.Where(ShouldIncludeAttributeDuringComparison)
.OrderBy(attr => attr.Name.ToString())
.ToList();
return orderedExpected.SequenceEqual(orderedActual, AttributeEqualityComparer);
}
private static bool AreEqual(XAttribute expected, XAttribute actual)
{
return expected.Name == actual.Name
&& expected.Value == actual.Value;
}
private static bool ShouldIncludeAttributeDuringComparison(XAttribute attribute)
{
// exclude 'xmlns' attributes since they're already considered in the
// actual element and attribute names
return attribute.Name != (XName)"xmlns"
&& attribute.Name.Namespace != XNamespace.Xmlns;
}
private static bool ShouldIncludeNodeDuringComparison(XNode node)
{
if (node is XComment)
{
return false; // not contextually relevant
}
if (node is XText /* includes XCData */ || node is XElement)
{
return true; // relevant
}
throw new NotSupportedException(String.Format("Node of type '{0}' is not supported.", node.GetType().Name));
}
}
private sealed class CallbackBasedEqualityComparer : IEqualityComparer
{
private readonly Func _equalityCheck;
public CallbackBasedEqualityComparer(Func equalityCheck)
{
_equalityCheck = equalityCheck;
}
public bool Equals(T x, T y)
{
return _equalityCheck(x, y);
}
public int GetHashCode(T obj)
{
return obj.ToString().GetHashCode();
}
}
}
}