PageRenderTime 66ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/test/System.Net.Http.Formatting.Test.Unit/HttpContentMultipartExtensionsTests.cs

https://bitbucket.org/mdavid/aspnetwebstack
C# | 511 lines | 446 code | 65 blank | 0 comment | 4 complexity | 02a00cec5ce0b4d136a817ad97968da5 MD5 | raw file
  1. using System.Collections.Generic;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Net.Http.Formatting.Parsers;
  5. using System.Net.Http.Headers;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using Microsoft.TestCommon;
  9. using Xunit;
  10. using Xunit.Extensions;
  11. using Assert = Microsoft.TestCommon.AssertEx;
  12. using FactAttribute = Microsoft.TestCommon.DefaultTimeoutFactAttribute;
  13. using TheoryAttribute = Microsoft.TestCommon.DefaultTimeoutTheoryAttribute;
  14. namespace System.Net.Http
  15. {
  16. public class HttpContentMultipartExtensionsTests
  17. {
  18. private const string DefaultContentType = "text/plain";
  19. private const string DefaultContentDisposition = "form-data";
  20. private const string ExceptionStreamProviderMessage = "Bad Stream Provider!";
  21. private const string ExceptionSyncStreamMessage = "Bad Sync Stream!";
  22. private const string ExceptionAsyncStreamMessage = "Bad Async Stream!";
  23. private const string LongText = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
  24. [Fact]
  25. [Trait("Description", "HttpContentMultipartExtensionMethods is a public static class")]
  26. public void TypeIsCorrect()
  27. {
  28. Assert.Type.HasProperties(
  29. typeof(HttpContentMultipartExtensions),
  30. TypeAssert.TypeProperties.IsPublicVisibleClass |
  31. TypeAssert.TypeProperties.IsStatic);
  32. }
  33. private static HttpContent CreateContent(string boundary, params string[] bodyEntity)
  34. {
  35. List<string> entities = new List<string>();
  36. int cnt = 0;
  37. foreach (var body in bodyEntity)
  38. {
  39. byte[] header = InternetMessageFormatHeaderParserTests.CreateBuffer(
  40. String.Format("N{0}: V{0}", cnt),
  41. String.Format("Content-Type: {0}", DefaultContentType),
  42. String.Format("Content-Disposition: {0}; FileName=\"N{1}\"", DefaultContentDisposition, cnt));
  43. entities.Add(Encoding.UTF8.GetString(header) + body);
  44. cnt++;
  45. }
  46. byte[] message = MimeMultipartParserTests.CreateBuffer(boundary, entities.ToArray());
  47. HttpContent result = new ByteArrayContent(message);
  48. var contentType = new MediaTypeHeaderValue("multipart/form-data");
  49. contentType.Parameters.Add(new NameValueHeaderValue("boundary", String.Format("\"{0}\"", boundary)));
  50. result.Headers.ContentType = contentType;
  51. return result;
  52. }
  53. private static void ValidateContents(IEnumerable<HttpContent> contents)
  54. {
  55. int cnt = 0;
  56. foreach (var content in contents)
  57. {
  58. Assert.NotNull(content);
  59. Assert.NotNull(content.Headers);
  60. Assert.Equal(4, content.Headers.Count());
  61. IEnumerable<string> parsedValues = content.Headers.GetValues(String.Format("N{0}", cnt));
  62. Assert.Equal(1, parsedValues.Count());
  63. Assert.Equal(String.Format("V{0}", cnt), parsedValues.ElementAt(0));
  64. Assert.Equal(DefaultContentType, content.Headers.ContentType.MediaType);
  65. Assert.Equal(DefaultContentDisposition, content.Headers.ContentDisposition.DispositionType);
  66. Assert.Equal(String.Format("\"N{0}\"", cnt), content.Headers.ContentDisposition.FileName);
  67. cnt++;
  68. }
  69. }
  70. [Fact]
  71. public void ReadAsMultipartAsync_DetectsNonMultipartContent()
  72. {
  73. Assert.ThrowsArgumentNull(() => HttpContentMultipartExtensions.IsMimeMultipartContent(null), "content");
  74. Assert.ThrowsArgument(() => new ByteArrayContent(new byte[0]).ReadAsMultipartAsync().Result, "content");
  75. Assert.ThrowsArgument(() => new StringContent(String.Empty).ReadAsMultipartAsync().Result, "content");
  76. Assert.ThrowsArgument(() => new StringContent(String.Empty, Encoding.UTF8, "multipart/form-data").ReadAsMultipartAsync().Result, "content");
  77. }
  78. public static IEnumerable<object[]> Boundaries
  79. {
  80. get { return ParserData.Boundaries; }
  81. }
  82. [Fact]
  83. public void ReadAsMultipartAsync_NullStreamProviderThrows()
  84. {
  85. HttpContent content = CreateContent("---");
  86. Assert.ThrowsArgumentNull(() =>
  87. {
  88. content.ReadAsMultipartAsync(null);
  89. }, "streamProvider");
  90. }
  91. [Theory]
  92. [PropertyData("Boundaries")]
  93. [Trait("Description", "ReadAsMultipartAsync(HttpContent, IMultipartStreamProvider, int) throws on buffersize.")]
  94. public void ReadAsMultipartAsyncStreamProviderThrowsOnBufferSize(string boundary)
  95. {
  96. HttpContent content = CreateContent(boundary);
  97. Assert.NotNull(content);
  98. Assert.ThrowsArgumentGreaterThanOrEqualTo(
  99. () => content.ReadAsMultipartAsync(new MemoryStreamProvider(), ParserData.MinBufferSize - 1),
  100. "bufferSize", ParserData.MinBufferSize.ToString(), ParserData.MinBufferSize - 1);
  101. }
  102. [Fact]
  103. [Trait("Description", "IsMimeMultipartContent(HttpContent) checks extension method arguments.")]
  104. public void IsMimeMultipartContentVerifyArguments()
  105. {
  106. Assert.ThrowsArgumentNull(() =>
  107. {
  108. HttpContent content = null;
  109. HttpContentMultipartExtensions.IsMimeMultipartContent(content);
  110. }, "content");
  111. }
  112. [Fact]
  113. public void IsMumeMultipartContentReturnsFalseForEmptyValues()
  114. {
  115. Assert.False(new ByteArrayContent(new byte[] { }).IsMimeMultipartContent(), "HttpContent should not be valid MIME multipart content");
  116. Assert.False(new StringContent(String.Empty).IsMimeMultipartContent(), "HttpContent should not be valid MIME multipart content");
  117. Assert.False(new StringContent(String.Empty, Encoding.UTF8, "multipart/form-data").IsMimeMultipartContent(), "HttpContent should not be valid MIME multipart content");
  118. }
  119. [Theory]
  120. [PropertyData("Boundaries")]
  121. [Trait("Description", "IsMimeMultipartContent(HttpContent) responds correctly to MIME multipart and other content")]
  122. public void IsMimeMultipartContent(string boundary)
  123. {
  124. HttpContent content = CreateContent(boundary);
  125. Assert.NotNull(content);
  126. Assert.True(content.IsMimeMultipartContent());
  127. }
  128. [Theory]
  129. [PropertyData("Boundaries")]
  130. [Trait("Description", "IsMimeMultipartContent(HttpContent, string) throws on null string.")]
  131. public void IsMimeMultipartContentThrowsOnNullString(string boundary)
  132. {
  133. HttpContent content = CreateContent(boundary);
  134. Assert.NotNull(content);
  135. foreach (var subtype in CommonUnitTestDataSets.EmptyStrings)
  136. {
  137. Assert.ThrowsArgumentNull(() =>
  138. {
  139. content.IsMimeMultipartContent(subtype);
  140. }, "subtype");
  141. }
  142. }
  143. [Fact]
  144. public void ReadAsMultipartAsync_SuccessfullyParsesContent()
  145. {
  146. HttpContent successContent;
  147. Task<IEnumerable<HttpContent>> task;
  148. IEnumerable<HttpContent> result;
  149. successContent = CreateContent("boundary", "A", "B", "C");
  150. task = successContent.ReadAsMultipartAsync();
  151. task.Wait(TimeoutConstant.DefaultTimeout);
  152. result = task.Result;
  153. Assert.Equal(3, result.Count());
  154. successContent = CreateContent("boundary", "A", "B", "C");
  155. task = successContent.ReadAsMultipartAsync(new MemoryStreamProvider());
  156. task.Wait(TimeoutConstant.DefaultTimeout);
  157. result = task.Result;
  158. Assert.Equal(3, result.Count());
  159. successContent = CreateContent("boundary", "A", "B", "C");
  160. task = successContent.ReadAsMultipartAsync(new MemoryStreamProvider(), 1024);
  161. task.Wait(TimeoutConstant.DefaultTimeout);
  162. result = task.Result;
  163. Assert.Equal(3, result.Count());
  164. }
  165. [Theory]
  166. [PropertyData("Boundaries")]
  167. public void ReadAsMultipartAsync_ParsesEmptyContentSuccessfully(string boundary)
  168. {
  169. HttpContent content = CreateContent(boundary);
  170. Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync();
  171. task.Wait(TimeoutConstant.DefaultTimeout);
  172. IEnumerable<HttpContent> result = task.Result;
  173. Assert.Equal(0, result.Count());
  174. }
  175. [Fact]
  176. public void ReadAsMultipartAsync_WithBadStreamProvider_Throws()
  177. {
  178. HttpContent content = CreateContent("--", "A", "B", "C");
  179. var invalidOperationException = Assert.Throws<InvalidOperationException>(
  180. () => content.ReadAsMultipartAsync(new BadStreamProvider()).Result,
  181. "The stream provider of type 'BadStreamProvider' threw an exception."
  182. );
  183. Assert.NotNull(invalidOperationException.InnerException);
  184. Assert.Equal(ExceptionStreamProviderMessage, invalidOperationException.InnerException.Message);
  185. }
  186. [Fact]
  187. public void ReadAsMultipartAsync_NullStreamProvider_Throws()
  188. {
  189. HttpContent content = CreateContent("--", "A", "B", "C");
  190. Assert.Throws<InvalidOperationException>(
  191. () => content.ReadAsMultipartAsync(new NullStreamProvider()).Result,
  192. "The stream provider of type 'NullStreamProvider' returned null. It must return a writable 'Stream' instance."
  193. );
  194. }
  195. [Fact]
  196. public void ReadAsMultipartAsync_ReadOnlyStream_Throws()
  197. {
  198. HttpContent content = CreateContent("--", "A", "B", "C");
  199. Assert.Throws<InvalidOperationException>(
  200. () => content.ReadAsMultipartAsync(new ReadOnlyStreamProvider()).Result,
  201. "The stream provider of type 'ReadOnlyStreamProvider' returned a read-only stream. It must return a writable 'Stream' instance."
  202. );
  203. }
  204. [Fact]
  205. public void ReadAsMultipartAsync_PrematureEndOfStream_Throws()
  206. {
  207. HttpContent content = new StreamContent(Stream.Null);
  208. var contentType = new MediaTypeHeaderValue("multipart/form-data");
  209. contentType.Parameters.Add(new NameValueHeaderValue("boundary", "\"{--\""));
  210. content.Headers.ContentType = contentType;
  211. Assert.Throws<IOException>(
  212. () => content.ReadAsMultipartAsync().Result,
  213. "Unexpected end of MIME multipart stream. MIME multipart message is not complete."
  214. );
  215. }
  216. [Fact]
  217. public void ReadAsMultipartAsync_ReadErrorOnStream_Throws()
  218. {
  219. HttpContent content = new StreamContent(new ReadErrorStream());
  220. var contentType = new MediaTypeHeaderValue("multipart/form-data");
  221. contentType.Parameters.Add(new NameValueHeaderValue("boundary", "\"--\""));
  222. content.Headers.ContentType = contentType;
  223. var ioException = Assert.Throws<IOException>(
  224. () => content.ReadAsMultipartAsync().Result,
  225. "Error reading MIME multipart body part."
  226. );
  227. Assert.NotNull(ioException.InnerException);
  228. Assert.Equal(ExceptionAsyncStreamMessage, ioException.InnerException.Message);
  229. }
  230. [Fact]
  231. public void ReadAsMultipartAsync_WriteErrorOnStream_Throws()
  232. {
  233. HttpContent content = CreateContent("--", "A", "B", "C");
  234. var ioException = Assert.Throws<IOException>(
  235. () => content.ReadAsMultipartAsync(new WriteErrorStreamProvider()).Result,
  236. "Error writing MIME multipart body part to output stream."
  237. );
  238. Assert.NotNull(ioException.InnerException);
  239. Assert.Equal(ExceptionAsyncStreamMessage, ioException.InnerException.Message);
  240. }
  241. [Theory]
  242. [PropertyData("Boundaries")]
  243. public void ReadAsMultipartAsync_SingleShortBodyPart_ParsesSuccessfully(string boundary)
  244. {
  245. HttpContent content = CreateContent(boundary, "A");
  246. IEnumerable<HttpContent> result = content.ReadAsMultipartAsync().Result;
  247. Assert.Equal(1, result.Count());
  248. Assert.Equal("A", result.ElementAt(0).ReadAsStringAsync().Result);
  249. ValidateContents(result);
  250. }
  251. [Theory]
  252. [PropertyData("Boundaries")]
  253. public void ReadAsMultipartAsync_MultipleShortBodyParts_ParsesSuccessfully(string boundary)
  254. {
  255. string[] text = new string[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
  256. HttpContent content = CreateContent(boundary, text);
  257. IEnumerable<HttpContent> result = content.ReadAsMultipartAsync().Result;
  258. Assert.Equal(text.Length, result.Count());
  259. for (var check = 0; check < text.Length; check++)
  260. {
  261. Assert.Equal(text[check], result.ElementAt(check).ReadAsStringAsync().Result);
  262. }
  263. ValidateContents(result);
  264. }
  265. [Theory]
  266. [PropertyData("Boundaries")]
  267. [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses with single long body asynchronously.")]
  268. public void ReadAsMultipartAsyncSingleLongBodyPartAsync(string boundary)
  269. {
  270. HttpContent content = CreateContent(boundary, LongText);
  271. Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync();
  272. task.Wait(TimeoutConstant.DefaultTimeout);
  273. IEnumerable<HttpContent> result = task.Result;
  274. Assert.Equal(1, result.Count());
  275. Assert.Equal(LongText, result.ElementAt(0).ReadAsStringAsync().Result);
  276. ValidateContents(result);
  277. }
  278. [Theory]
  279. [PropertyData("Boundaries")]
  280. [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses with multiple long bodies asynchronously.")]
  281. public void ReadAsMultipartAsyncMultipleLongBodyPartsAsync(string boundary)
  282. {
  283. string[] text = new string[] {
  284. "A" + LongText + "A",
  285. "B" + LongText + "B",
  286. "C" + LongText + "C",
  287. "D" + LongText + "D",
  288. "E" + LongText + "E",
  289. "F" + LongText + "F",
  290. "G" + LongText + "G",
  291. "H" + LongText + "H",
  292. "I" + LongText + "I",
  293. "J" + LongText + "J",
  294. "K" + LongText + "K",
  295. "L" + LongText + "L",
  296. "M" + LongText + "M",
  297. "N" + LongText + "N",
  298. "O" + LongText + "O",
  299. "P" + LongText + "P",
  300. "Q" + LongText + "Q",
  301. "R" + LongText + "R",
  302. "S" + LongText + "S",
  303. "T" + LongText + "T",
  304. "U" + LongText + "U",
  305. "V" + LongText + "V",
  306. "W" + LongText + "W",
  307. "X" + LongText + "X",
  308. "Y" + LongText + "Y",
  309. "Z" + LongText + "Z"};
  310. HttpContent content = CreateContent(boundary, text);
  311. Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync(new MemoryStreamProvider(), ParserData.MinBufferSize);
  312. task.Wait(TimeoutConstant.DefaultTimeout);
  313. IEnumerable<HttpContent> result = task.Result;
  314. Assert.Equal(text.Length, result.Count());
  315. for (var check = 0; check < text.Length; check++)
  316. {
  317. Assert.Equal(text[check], result.ElementAt(check).ReadAsStringAsync().Result);
  318. }
  319. ValidateContents(result);
  320. }
  321. [Theory]
  322. [PropertyData("Boundaries")]
  323. [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses content generated by MultipartContent asynchronously.")]
  324. public void ReadAsMultipartAsyncUsingMultipartContentAsync(string boundary)
  325. {
  326. MultipartContent content = new MultipartContent("mixed", boundary);
  327. content.Add(new StringContent("A"));
  328. content.Add(new StringContent("B"));
  329. content.Add(new StringContent("C"));
  330. MemoryStream memStream = new MemoryStream();
  331. content.CopyToAsync(memStream).Wait();
  332. memStream.Position = 0;
  333. byte[] data = memStream.ToArray();
  334. var byteContent = new ByteArrayContent(data);
  335. byteContent.Headers.ContentType = content.Headers.ContentType;
  336. Task<IEnumerable<HttpContent>> task = byteContent.ReadAsMultipartAsync();
  337. task.Wait(TimeoutConstant.DefaultTimeout);
  338. IEnumerable<HttpContent> result = task.Result;
  339. Assert.Equal(3, result.Count());
  340. Assert.Equal("A", result.ElementAt(0).ReadAsStringAsync().Result);
  341. Assert.Equal("B", result.ElementAt(1).ReadAsStringAsync().Result);
  342. Assert.Equal("C", result.ElementAt(2).ReadAsStringAsync().Result);
  343. }
  344. [Theory]
  345. [PropertyData("Boundaries")]
  346. [Trait("Description", "ReadAsMultipartAsync(HttpContent) parses nested content generated by MultipartContent asynchronously.")]
  347. public void ReadAsMultipartAsyncNestedMultipartContentAsync(string boundary)
  348. {
  349. const int nesting = 10;
  350. const string innerText = "Content";
  351. MultipartContent innerContent = new MultipartContent("mixed", boundary);
  352. innerContent.Add(new StringContent(innerText));
  353. for (var cnt = 0; cnt < nesting; cnt++)
  354. {
  355. string outerBoundary = String.Format("{0}_{1}", boundary, cnt);
  356. MultipartContent outerContent = new MultipartContent("mixed", outerBoundary);
  357. outerContent.Add(innerContent);
  358. innerContent = outerContent;
  359. }
  360. MemoryStream memStream = new MemoryStream();
  361. innerContent.CopyToAsync(memStream).Wait();
  362. memStream.Position = 0;
  363. byte[] data = memStream.ToArray();
  364. HttpContent content = new ByteArrayContent(data);
  365. content.Headers.ContentType = innerContent.Headers.ContentType;
  366. for (var cnt = 0; cnt < nesting + 1; cnt++)
  367. {
  368. Task<IEnumerable<HttpContent>> task = content.ReadAsMultipartAsync();
  369. task.Wait(TimeoutConstant.DefaultTimeout);
  370. IEnumerable<HttpContent> result = task.Result;
  371. Assert.Equal(1, result.Count());
  372. content = result.ElementAt(0);
  373. Assert.NotNull(content);
  374. }
  375. string text = content.ReadAsStringAsync().Result;
  376. Assert.Equal(innerText, text);
  377. }
  378. public class ReadOnlyStream : MemoryStream
  379. {
  380. public override bool CanWrite
  381. {
  382. get
  383. {
  384. return false;
  385. }
  386. }
  387. }
  388. public class ReadErrorStream : MemoryStream
  389. {
  390. public override int Read(byte[] buffer, int offset, int count)
  391. {
  392. throw new Exception(ExceptionSyncStreamMessage);
  393. }
  394. public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
  395. {
  396. throw new Exception(ExceptionAsyncStreamMessage);
  397. }
  398. }
  399. public class WriteErrorStream : MemoryStream
  400. {
  401. public override void Write(byte[] buffer, int offset, int count)
  402. {
  403. throw new Exception(ExceptionSyncStreamMessage);
  404. }
  405. public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
  406. {
  407. throw new Exception(ExceptionAsyncStreamMessage);
  408. }
  409. }
  410. public class MemoryStreamProvider : IMultipartStreamProvider
  411. {
  412. public Stream GetStream(HttpContentHeaders headers)
  413. {
  414. return new MemoryStream();
  415. }
  416. }
  417. public class BadStreamProvider : IMultipartStreamProvider
  418. {
  419. public Stream GetStream(HttpContentHeaders headers)
  420. {
  421. throw new Exception(ExceptionStreamProviderMessage);
  422. }
  423. }
  424. public class NullStreamProvider : IMultipartStreamProvider
  425. {
  426. public Stream GetStream(HttpContentHeaders headers)
  427. {
  428. return null;
  429. }
  430. }
  431. public class ReadOnlyStreamProvider : IMultipartStreamProvider
  432. {
  433. public Stream GetStream(HttpContentHeaders headers)
  434. {
  435. return new ReadOnlyStream();
  436. }
  437. }
  438. public class WriteErrorStreamProvider : IMultipartStreamProvider
  439. {
  440. public Stream GetStream(HttpContentHeaders headers)
  441. {
  442. return new WriteErrorStream();
  443. }
  444. }
  445. }
  446. }