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