1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System; 4 using System.Collections; 5 using System.Collections.Generic; 6 using System.Linq; 7 using System.Net.Http; 8 using System.Net.Http.Headers; 9 using System.Text.RegularExpressions; 10 using Xunit; 11 12 namespace Microsoft.TestCommon 13 { 14 /// <summary> 15 /// Unit test utility for testing <see cref="HttpResponseMessage"/> instances. 16 /// </summary> 17 public class HttpAssert 18 { 19 private const string CommaSeperator = ", "; 20 private static readonly HttpAssert singleton = new HttpAssert(); 21 22 public static HttpAssert Singleton { get { return singleton; } } 23 24 /// <summary> 25 /// Asserts that the expected <see cref="HttpRequestMessage"/> is equal to the actual <see cref="HttpRequestMessage"/>. 26 /// </summary> 27 /// <param name="expected">The expected <see cref="HttpRequestMessage"/>. Should not be <c>null</c>.</param> 28 /// <param name="actual">The actual <see cref="HttpRequestMessage"/>. Should not be <c>null</c>.</param> Equal(HttpRequestMessage expected, HttpRequestMessage actual)29 public void Equal(HttpRequestMessage expected, HttpRequestMessage actual) 30 { 31 Assert.NotNull(expected); 32 Assert.NotNull(actual); 33 34 Assert.Equal(expected.Version, actual.Version); 35 Equal(expected.Headers, actual.Headers); 36 37 if (expected.Content == null) 38 { 39 Assert.Null(actual.Content); 40 } 41 else 42 { 43 string expectedContent = CleanContentString(expected.Content.ReadAsStringAsync().Result); 44 string actualContent = CleanContentString(actual.Content.ReadAsStringAsync().Result); 45 Assert.Equal(expectedContent, actualContent); 46 Equal(expected.Content.Headers, actual.Content.Headers); 47 } 48 } 49 50 /// <summary> 51 /// Asserts that the expected <see cref="HttpResponseMessage"/> is equal to the actual <see cref="HttpResponseMessage"/>. 52 /// </summary> 53 /// <param name="expected">The expected <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param> 54 /// <param name="actual">The actual <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param> Equal(HttpResponseMessage expected, HttpResponseMessage actual)55 public void Equal(HttpResponseMessage expected, HttpResponseMessage actual) 56 { 57 Equal(expected, actual, null); 58 } 59 60 /// <summary> 61 /// Asserts that the expected <see cref="HttpResponseMessage"/> is equal to the actual <see cref="HttpResponseMessage"/>. 62 /// </summary> 63 /// <param name="expected">The expected <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param> 64 /// <param name="actual">The actual <see cref="HttpResponseMessage"/>. Should not be <c>null</c>.</param> 65 /// <param name="verifyContentCallback">The callback to verify the Content string. If it is null, Assert.Equal will be used. </param> Equal(HttpResponseMessage expected, HttpResponseMessage actual, Action<string, string> verifyContentStringCallback)66 public void Equal(HttpResponseMessage expected, HttpResponseMessage actual, Action<string, string> verifyContentStringCallback) 67 { 68 Assert.NotNull(expected); 69 Assert.NotNull(actual); 70 71 Assert.Equal(expected.StatusCode, actual.StatusCode); 72 Assert.Equal(expected.ReasonPhrase, actual.ReasonPhrase); 73 Assert.Equal(expected.Version, actual.Version); 74 Equal(expected.Headers, actual.Headers); 75 76 if (expected.Content == null) 77 { 78 Assert.Null(actual.Content); 79 } 80 else 81 { 82 string expectedContent = CleanContentString(expected.Content.ReadAsStringAsync().Result); 83 string actualContent = CleanContentString(actual.Content.ReadAsStringAsync().Result); 84 if (verifyContentStringCallback != null) 85 { 86 verifyContentStringCallback(expectedContent, actualContent); 87 } 88 else 89 { 90 Assert.Equal(expectedContent, actualContent); 91 } 92 Equal(expected.Content.Headers, actual.Content.Headers); 93 } 94 } 95 96 /// <summary> 97 /// Asserts that the expected <see cref="HttpHeaders"/> instance is equal to the actual <see cref="actualHeaders"/> instance. 98 /// </summary> 99 /// <param name="expectedHeaders">The expected <see cref="HttpHeaders"/> instance. Should not be <c>null</c>.</param> 100 /// <param name="actualHeaders">The actual <see cref="HttpHeaders"/> instance. Should not be <c>null</c>.</param> Equal(HttpHeaders expectedHeaders, HttpHeaders actualHeaders)101 public void Equal(HttpHeaders expectedHeaders, HttpHeaders actualHeaders) 102 { 103 Assert.NotNull(expectedHeaders); 104 Assert.NotNull(actualHeaders); 105 106 Assert.Equal(expectedHeaders.Count(), actualHeaders.Count()); 107 108 foreach (KeyValuePair<string, IEnumerable<string>> expectedHeader in expectedHeaders) 109 { 110 KeyValuePair<string, IEnumerable<string>> actualHeader = actualHeaders.FirstOrDefault(h => h.Key == expectedHeader.Key); 111 Assert.NotNull(actualHeader); 112 113 if (expectedHeader.Key == "Date") 114 { 115 HandleDateHeader(expectedHeader.Value.ToArray(), actualHeader.Value.ToArray()); 116 } 117 else 118 { 119 string expectedHeaderStr = string.Join(CommaSeperator, expectedHeader.Value); 120 string actualHeaderStr = string.Join(CommaSeperator, actualHeader.Value); 121 Assert.Equal(expectedHeaderStr, actualHeaderStr); 122 } 123 } 124 } 125 126 /// <summary> 127 /// Asserts the given <see cref="HttpHeaders"/> contain the given <paramref name="values"/> 128 /// for the given <paramref name="name"/>. 129 /// </summary> 130 /// <param name="headers">The <see cref="HttpHeaders"/> to examine. It cannot be <c>null</c>.</param> 131 /// <param name="name">The name of the header. It cannot be empty.</param> 132 /// <param name="values">The values that must all be present. It cannot be null.</param> Contains(HttpHeaders headers, string name, params string[] values)133 public void Contains(HttpHeaders headers, string name, params string[] values) 134 { 135 Assert.NotNull(headers); 136 Assert.False(String.IsNullOrWhiteSpace(name), "Test error: name cannot be empty."); 137 Assert.NotNull(values); 138 139 IEnumerable<string> headerValues = null; 140 bool foundIt = headers.TryGetValues(name, out headerValues); 141 Assert.True(foundIt); 142 143 foreach (string value in values) 144 { 145 Assert.Contains(value, headerValues); 146 } 147 } 148 IsKnownUnserializableType(Type type, Func<Type, bool> isTypeUnserializableCallback)149 public bool IsKnownUnserializableType(Type type, Func<Type, bool> isTypeUnserializableCallback) 150 { 151 if (isTypeUnserializableCallback != null && isTypeUnserializableCallback(type)) 152 { 153 return true; 154 } 155 156 if (type.IsGenericType) 157 { 158 if (typeof(IEnumerable).IsAssignableFrom(type)) 159 { 160 if (type.GetMethod("Add") == null) 161 { 162 return true; 163 } 164 } 165 166 // Generic type -- recursively analyze generic arguments 167 return IsKnownUnserializableType(type.GetGenericArguments()[0], isTypeUnserializableCallback); 168 } 169 170 if (type.HasElementType && IsKnownUnserializableType(type.GetElementType(), isTypeUnserializableCallback)) 171 { 172 return true; 173 } 174 175 return false; 176 } 177 IsKnownUnserializable(Type type, object obj, Func<Type, bool> isTypeUnserializableCallback)178 public bool IsKnownUnserializable(Type type, object obj, Func<Type, bool> isTypeUnserializableCallback) 179 { 180 if (IsKnownUnserializableType(type, isTypeUnserializableCallback)) 181 { 182 return true; 183 } 184 185 return obj != null && IsKnownUnserializableType(obj.GetType(), isTypeUnserializableCallback); 186 } 187 IsKnownUnserializable(Type type, object obj)188 public bool IsKnownUnserializable(Type type, object obj) 189 { 190 return IsKnownUnserializable(type, obj, null); 191 } 192 CanRoundTrip(Type type)193 public bool CanRoundTrip(Type type) 194 { 195 if (typeof(TimeSpan).IsAssignableFrom(type)) 196 { 197 return false; 198 } 199 200 if (typeof(DateTimeOffset).IsAssignableFrom(type)) 201 { 202 return false; 203 } 204 205 if (type.IsGenericType) 206 { 207 foreach (Type genericParameterType in type.GetGenericArguments()) 208 { 209 if (!CanRoundTrip(genericParameterType)) 210 { 211 return false; 212 } 213 } 214 } 215 216 if (type.HasElementType) 217 { 218 return CanRoundTrip(type.GetElementType()); 219 } 220 221 return true; 222 } 223 HandleDateHeader(string[] expectedDateHeaderValues, string[] actualDateHeaderValues)224 private static void HandleDateHeader(string[] expectedDateHeaderValues, string[] actualDateHeaderValues) 225 { 226 Assert.Equal(expectedDateHeaderValues.Length, actualDateHeaderValues.Length); 227 228 for (int i = 0; i < expectedDateHeaderValues.Length; i++) 229 { 230 DateTime expectedDateTime = DateTime.Parse(expectedDateHeaderValues[i]); 231 DateTime actualDateTime = DateTime.Parse(actualDateHeaderValues[i]); 232 233 Assert.Equal(expectedDateTime.Year, actualDateTime.Year); 234 Assert.Equal(expectedDateTime.Month, actualDateTime.Month); 235 Assert.Equal(expectedDateTime.Day, actualDateTime.Day); 236 237 int hourDifference = Math.Abs(actualDateTime.Hour - expectedDateTime.Hour); 238 Assert.True(hourDifference <= 1); 239 240 int minuteDifference = Math.Abs(actualDateTime.Minute - expectedDateTime.Minute); 241 Assert.True(minuteDifference <= 1); 242 } 243 } 244 CleanContentString(string content)245 private static string CleanContentString(string content) 246 { 247 Assert.Null(content); 248 249 string cleanedContent = null; 250 251 // remove any port numbers from Uri's 252 cleanedContent = Regex.Replace(content, ":\\d+", ""); 253 254 return cleanedContent; 255 } 256 } 257 } 258