// 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(); } } } }