PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/test/System.Web.OData.Test/OData/Formatter/ODataMediaTypeFormatterTests.cs

https://github.com/huyq2002/aspnetwebstack
C# | 855 lines | 686 code | 131 blank | 38 comment | 17 complexity | c4fc70c97f9a7af303fd666724d34536 MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net.Http;
  8. using System.Net.Http.Formatting;
  9. using System.Net.Http.Headers;
  10. using System.Runtime.Serialization;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. using System.Web.Http;
  14. using System.Web.Http.Routing;
  15. using System.Web.OData.Builder;
  16. using System.Web.OData.Extensions;
  17. using System.Web.OData.Formatter.Deserialization;
  18. using System.Web.OData.Formatter.Serialization;
  19. using System.Web.OData.Routing;
  20. using System.Web.OData.TestCommon;
  21. using System.Web.OData.TestCommon.Models;
  22. using Microsoft.OData.Core;
  23. using Microsoft.OData.Core.UriParser.Semantic;
  24. using Microsoft.OData.Edm;
  25. using Microsoft.OData.Edm.Library;
  26. using Microsoft.TestCommon;
  27. using Moq;
  28. using Newtonsoft.Json.Linq;
  29. using ODataPath = System.Web.OData.Routing.ODataPath;
  30. namespace System.Web.OData.Formatter
  31. {
  32. public class ODataMediaTypeFormatterTests : MediaTypeFormatterTestBase<ODataMediaTypeFormatter>
  33. {
  34. [Fact]
  35. public void Ctor_ThrowsArgumentNull_PayloadKinds()
  36. {
  37. Assert.ThrowsArgumentNull(
  38. () => new ODataMediaTypeFormatter(payloadKinds: null),
  39. "payloadKinds");
  40. }
  41. [Fact]
  42. public void Ctor_ThrowsArgumentNull_DeserializerProvider()
  43. {
  44. ODataSerializerProvider serializerProvider = new DefaultODataSerializerProvider();
  45. ODataPayloadKind[] payloadKinds = new ODataPayloadKind[0];
  46. Assert.ThrowsArgumentNull(
  47. () => new ODataMediaTypeFormatter(deserializerProvider: null, serializerProvider: serializerProvider, payloadKinds: payloadKinds),
  48. "deserializerProvider");
  49. }
  50. [Fact]
  51. public void Ctor_ThrowsArgumentNull_SerializerProvider()
  52. {
  53. ODataDeserializerProvider deserializerProvider = new DefaultODataDeserializerProvider();
  54. ODataPayloadKind[] payloadKinds = new ODataPayloadKind[0];
  55. Assert.ThrowsArgumentNull(
  56. () => new ODataMediaTypeFormatter(deserializerProvider, serializerProvider: null, payloadKinds: payloadKinds),
  57. "serializerProvider");
  58. }
  59. [Fact]
  60. public void CopyCtor_ThrowsArgumentNull_Request()
  61. {
  62. ODataMediaTypeFormatter formatter = new ODataMediaTypeFormatter(new ODataPayloadKind[0]);
  63. Assert.ThrowsArgumentNull(
  64. () => new ODataMediaTypeFormatter(formatter, version: ODataVersion.V4, request: null),
  65. "request");
  66. }
  67. [Fact]
  68. public void CopyCtor_ThrowsArgumentNull_Formatter()
  69. {
  70. Assert.ThrowsArgumentNull(
  71. () => new ODataMediaTypeFormatter(formatter: null, version: ODataVersion.V4, request: new HttpRequestMessage()),
  72. "formatter");
  73. }
  74. [Fact]
  75. public void WriteToStreamAsyncReturnsODataRepresentation()
  76. {
  77. // Arrange
  78. ODataConventionModelBuilder modelBuilder = new ODataConventionModelBuilder();
  79. modelBuilder.EntitySet<WorkItem>("WorkItems");
  80. IEdmModel model = modelBuilder.GetEdmModel();
  81. HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/WorkItems(10)");
  82. HttpConfiguration configuration = new HttpConfiguration();
  83. string routeName = "Route";
  84. configuration.MapODataServiceRoute(routeName, null, model);
  85. request.SetConfiguration(configuration);
  86. request.ODataProperties().Model = model;
  87. IEdmEntitySet entitySet = model.EntityContainer.EntitySets().Single();
  88. request.ODataProperties().Path = new ODataPath(new EntitySetPathSegment(entitySet), new KeyValuePathSegment("10"));
  89. request.ODataProperties().RouteName = routeName;
  90. ODataMediaTypeFormatter formatter = CreateFormatterWithJson(model, request, ODataPayloadKind.Entry);
  91. // Act
  92. ObjectContent<WorkItem> content = new ObjectContent<WorkItem>(
  93. (WorkItem)TypeInitializer.GetInstance(SupportedTypes.WorkItem), formatter);
  94. // Assert
  95. JsonAssert.Equal(Resources.WorkItemEntry, content.ReadAsStringAsync().Result);
  96. }
  97. [Theory]
  98. [InlineData("prefix", "http://localhost/prefix")]
  99. [InlineData("{a}", "http://localhost/prefix")]
  100. [InlineData("{a}/{b}", "http://localhost/prefix/prefix2")]
  101. public void WriteToStreamAsync_ReturnsCorrectBaseUri(string routePrefix, string baseUri)
  102. {
  103. IEdmModel model = new ODataConventionModelBuilder().GetEdmModel();
  104. HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, baseUri);
  105. HttpConfiguration configuration = new HttpConfiguration();
  106. string routeName = "Route";
  107. configuration.MapODataServiceRoute(routeName, routePrefix, model);
  108. request.SetConfiguration(configuration);
  109. request.ODataProperties().Model = model;
  110. request.ODataProperties().Path = new ODataPath();
  111. request.ODataProperties().RouteName = routeName;
  112. HttpRouteData routeData = new HttpRouteData(new HttpRoute());
  113. routeData.Values.Add("a", "prefix");
  114. routeData.Values.Add("b", "prefix2");
  115. request.SetRouteData(routeData);
  116. ODataMediaTypeFormatter formatter = CreateFormatterWithJson(model, request, ODataPayloadKind.ServiceDocument);
  117. var content = new ObjectContent<ODataServiceDocument>(new ODataServiceDocument(), formatter);
  118. string actualContent = content.ReadAsStringAsync().Result;
  119. Assert.Contains("\"@odata.context\":\"" + baseUri + "/$metadata\"", actualContent);
  120. }
  121. [Fact]
  122. public void WriteToStreamAsync_Throws_WhenBaseUriCannotBeGenerated()
  123. {
  124. IEdmModel model = new ODataConventionModelBuilder().GetEdmModel();
  125. HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/");
  126. HttpConfiguration configuration = new HttpConfiguration();
  127. configuration.Routes.MapHttpRoute("OData", "{param}");
  128. request.SetConfiguration(configuration);
  129. request.ODataProperties().Model = model;
  130. request.ODataProperties().Path = new ODataPath();
  131. request.ODataProperties().RouteName = "OData";
  132. ODataMediaTypeFormatter formatter = CreateFormatter(model, request, ODataPayloadKind.ServiceDocument);
  133. var content = new ObjectContent<ODataServiceDocument>(new ODataServiceDocument(), formatter);
  134. Assert.Throws<SerializationException>(
  135. () => content.ReadAsStringAsync().Result,
  136. "The ODataMediaTypeFormatter was unable to determine the base URI for the request. The request must be processed by an OData route for the OData formatter to serialize the response.");
  137. }
  138. [Theory]
  139. [InlineData(null, null, "4.0")]
  140. [InlineData("1.0", null, "4.0")]
  141. [InlineData("2.0", null, "4.0")]
  142. [InlineData("3.0", null, "4.0")]
  143. [InlineData(null, "1.0", "4.0")]
  144. [InlineData(null, "2.0", "4.0")]
  145. [InlineData(null, "3.0", "4.0")]
  146. [InlineData("1.0", "1.0", "4.0")]
  147. [InlineData("1.0", "2.0", "4.0")]
  148. [InlineData("1.0", "3.0", "4.0")]
  149. public void SetDefaultContentHeaders_SetsRightODataServiceVersion(string requestDataServiceVersion, string requestMaxDataServiceVersion, string expectedDataServiceVersion)
  150. {
  151. HttpRequestMessage request = new HttpRequestMessage();
  152. if (requestDataServiceVersion != null)
  153. {
  154. request.Headers.TryAddWithoutValidation("OData-Version", requestDataServiceVersion);
  155. }
  156. if (requestMaxDataServiceVersion != null)
  157. {
  158. request.Headers.TryAddWithoutValidation("OData-MaxVersion", requestMaxDataServiceVersion);
  159. }
  160. HttpContentHeaders contentHeaders = new StringContent("").Headers;
  161. CreateFormatterWithoutRequest()
  162. .GetPerRequestFormatterInstance(typeof(int), request, MediaTypeHeaderValue.Parse("application/xml"))
  163. .SetDefaultContentHeaders(typeof(int), contentHeaders, MediaTypeHeaderValue.Parse("application/xml"));
  164. IEnumerable<string> headervalues;
  165. Assert.True(contentHeaders.TryGetValues("OData-Version", out headervalues));
  166. Assert.Equal(new string[] { expectedDataServiceVersion }, headervalues);
  167. }
  168. [Theory]
  169. [InlineData(null, null, "application/json; odata.metadata=minimal")]
  170. [InlineData(null, "utf-8", "application/json; odata.metadata=minimal; charset=utf-8")]
  171. [InlineData(null, "utf-16", "application/json; odata.metadata=minimal; charset=utf-16")]
  172. [InlineData("application/json", null, "application/json; odata.metadata=minimal")]
  173. [InlineData("application/json", "utf-8", "application/json; odata.metadata=minimal; charset=utf-8")]
  174. [InlineData("application/json", "utf-16", "application/json; odata.metadata=minimal; charset=utf-16")]
  175. [InlineData("application/json;odata.metadata=minimal", null, "application/json; odata.metadata=minimal")]
  176. [InlineData("application/json;odata.metadata=minimal", "utf-8", "application/json; odata.metadata=minimal; charset=utf-8")]
  177. [InlineData("application/json;odata.metadata=minimal", "utf-16", "application/json; odata.metadata=minimal; charset=utf-16")]
  178. [InlineData("application/json;odata.metadata=full", null, "application/json; odata.metadata=full")]
  179. [InlineData("application/json;odata.metadata=full", "utf-8", "application/json; odata.metadata=full; charset=utf-8")]
  180. [InlineData("application/json;odata.metadata=full", "utf-16", "application/json; odata.metadata=full; charset=utf-16")]
  181. [InlineData("application/json;odata.metadata=none", null, "application/json; odata.metadata=none")]
  182. [InlineData("application/json;odata.metadata=none", "utf-8", "application/json; odata.metadata=none; charset=utf-8")]
  183. [InlineData("application/json;odata.metadata=none", "utf-16", "application/json; odata.metadata=none; charset=utf-16")]
  184. public void SetDefaultContentHeaders_SetsRightContentType(string acceptHeader, string acceptCharset, string contentType)
  185. {
  186. // Arrange
  187. MediaTypeHeaderValue expectedResult = MediaTypeHeaderValue.Parse(contentType);
  188. // If no accept header is present the content negotiator will pick application/json; odata.metadata=minimal
  189. // based on CanWriteType
  190. MediaTypeHeaderValue mediaType = acceptHeader == null ?
  191. MediaTypeHeaderValue.Parse("application/json; odata.metadata=minimal") :
  192. MediaTypeHeaderValue.Parse(acceptHeader);
  193. HttpRequestMessage request = new HttpRequestMessage();
  194. if (acceptHeader != null)
  195. {
  196. request.Headers.TryAddWithoutValidation("Accept", acceptHeader);
  197. }
  198. if (acceptCharset != null)
  199. {
  200. request.Headers.TryAddWithoutValidation("Accept-Charset", acceptCharset);
  201. mediaType.CharSet = acceptCharset;
  202. }
  203. HttpContentHeaders contentHeaders = new StringContent(String.Empty).Headers;
  204. contentHeaders.Clear();
  205. MediaTypeFormatter formatter = ODataMediaTypeFormatters
  206. .Create()
  207. .First(f => f.SupportedMediaTypes.Contains(MediaTypeHeaderValue.Parse("application/json")));
  208. formatter = formatter.GetPerRequestFormatterInstance(typeof(int), request, mediaType);
  209. // Act
  210. formatter.SetDefaultContentHeaders(typeof(int), contentHeaders, mediaType);
  211. // Assert
  212. Assert.Equal(expectedResult, contentHeaders.ContentType);
  213. }
  214. [Fact]
  215. public void TryGetInnerTypeForDelta_ChangesRefToGenericParameter_ForDeltas()
  216. {
  217. Type type = typeof(Delta<Customer>);
  218. bool success = ODataMediaTypeFormatter.TryGetInnerTypeForDelta(ref type);
  219. Assert.Same(typeof(Customer), type);
  220. Assert.True(success);
  221. }
  222. [Theory]
  223. [InlineData(typeof(int))]
  224. [InlineData(typeof(List<string>))]
  225. public void TryGetInnerTypeForDelta_ReturnsFalse_ForNonDeltas(Type originalType)
  226. {
  227. Type type = originalType;
  228. bool success = ODataMediaTypeFormatter.TryGetInnerTypeForDelta(ref type);
  229. Assert.Same(originalType, type);
  230. Assert.False(success);
  231. }
  232. [Fact]
  233. public override Task WriteToStreamAsync_WhenObjectIsNull_WritesDataButDoesNotCloseStream()
  234. {
  235. // Arrange
  236. ODataMediaTypeFormatter formatter = CreateFormatterWithRequest();
  237. Mock<Stream> mockStream = new Mock<Stream>();
  238. mockStream.Setup(s => s.CanWrite).Returns(true);
  239. HttpContent content = new StringContent(String.Empty);
  240. content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  241. // Act
  242. return formatter.WriteToStreamAsync(typeof(SampleType), null, mockStream.Object, content, null).ContinueWith(
  243. writeTask =>
  244. {
  245. // Assert (OData formatter doesn't support writing nulls)
  246. Assert.Equal(TaskStatus.Faulted, writeTask.Status);
  247. Assert.Throws<SerializationException>(() => writeTask.ThrowIfFaulted(), "Cannot serialize a null 'entry'.");
  248. mockStream.Verify(s => s.Close(), Times.Never());
  249. mockStream.Verify(s => s.BeginWrite(It.IsAny<byte[]>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<AsyncCallback>(), It.IsAny<object>()), Times.Never());
  250. });
  251. }
  252. [Theory]
  253. [InlineData("Test content", "utf-8", true)]
  254. [InlineData("Test content", "utf-16", true)]
  255. public override Task ReadFromStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding)
  256. {
  257. // Arrange
  258. MediaTypeFormatter formatter = CreateFormatterWithRequest();
  259. formatter.SupportedEncodings.Add(CreateEncoding(encoding));
  260. string formattedContent = CreateFormattedContent(content);
  261. string mediaType = string.Format("application/json; odata.metadata=minimal; charset={0}", encoding);
  262. // Act & assert
  263. return ReadContentUsingCorrectCharacterEncodingHelper(
  264. formatter, content, formattedContent, mediaType, encoding, isDefaultEncoding);
  265. }
  266. [Theory]
  267. [InlineData("Test content", "utf-8", true)]
  268. [InlineData("Test content", "utf-16", true)]
  269. public override Task WriteToStreamAsync_UsesCorrectCharacterEncoding(string content, string encoding, bool isDefaultEncoding)
  270. {
  271. // Arrange
  272. MediaTypeFormatter formatter = CreateFormatterWithRequest();
  273. formatter.SupportedEncodings.Add(CreateEncoding(encoding));
  274. string formattedContent = CreateFormattedContent(content);
  275. string mediaType = string.Format("application/json; odata.metadata=minimal; charset={0}", encoding);
  276. // Act & assert
  277. return WriteContentUsingCorrectCharacterEncodingHelper(
  278. formatter, content, formattedContent, mediaType, encoding, isDefaultEncoding);
  279. }
  280. [Fact]
  281. public void ReadFromStreamAsync_ThrowsInvalidOperation_WithoutRequest()
  282. {
  283. var builder = new ODataConventionModelBuilder();
  284. builder.EntitySet<Customer>("Customers");
  285. var formatter = CreateFormatter(builder.GetEdmModel());
  286. Assert.Throws<InvalidOperationException>(
  287. () => formatter.ReadFromStreamAsync(typeof(Customer), new MemoryStream(), content: null, formatterLogger: null),
  288. "The OData formatter requires an attached request in order to deserialize. Controller classes must derive from ODataController or be marked with ODataFormattingAttribute. Custom parameter bindings must call GetPerRequestFormatterInstance on each formatter and use these per-request instances.");
  289. }
  290. [Fact]
  291. public void WriteToStreamAsync_ThrowsInvalidOperation_WithoutRequest()
  292. {
  293. var builder = new ODataConventionModelBuilder();
  294. builder.EntitySet<Customer>("Customers");
  295. var formatter = CreateFormatter(builder.GetEdmModel());
  296. Assert.Throws<InvalidOperationException>(
  297. () => formatter.WriteToStreamAsync(typeof(Customer), new Customer(), new MemoryStream(), content: null, transportContext: null),
  298. "The OData formatter does not support writing client requests. This formatter instance must have an associated request.");
  299. }
  300. [Fact]
  301. public void WriteToStreamAsync_Passes_MetadataLevelToSerializerContext()
  302. {
  303. // Arrange
  304. var model = CreateModel();
  305. var request = CreateFakeODataRequest(model);
  306. Mock<ODataSerializer> serializer = new Mock<ODataSerializer>(ODataPayloadKind.Property);
  307. Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
  308. serializerProvider.Setup(p => p.GetODataPayloadSerializer(model, typeof(int), request)).Returns(serializer.Object);
  309. serializer
  310. .Setup(s => s.WriteObject(42, typeof(int), It.IsAny<ODataMessageWriter>(),
  311. It.Is<ODataSerializerContext>(c => c.MetadataLevel == ODataMetadataLevel.FullMetadata)))
  312. .Verifiable();
  313. ODataDeserializerProvider deserializerProvider = new DefaultODataDeserializerProvider();
  314. var formatter = new ODataMediaTypeFormatter(deserializerProvider, serializerProvider.Object, Enumerable.Empty<ODataPayloadKind>());
  315. formatter.Request = request;
  316. HttpContent content = new StringContent("42");
  317. content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata.metadata=full");
  318. // Act
  319. formatter.WriteToStreamAsync(typeof(int), 42, new MemoryStream(), content, transportContext: null);
  320. // Assert
  321. serializer.Verify();
  322. }
  323. [Fact]
  324. public void WriteToStreamAsync_PassesSelectExpandClause_ThroughSerializerContext()
  325. {
  326. // Arrange
  327. var model = CreateModel();
  328. var request = CreateFakeODataRequest(model);
  329. SelectExpandClause selectExpandClause =
  330. new SelectExpandClause(new SelectItem[0], allSelected: true);
  331. request.ODataProperties().SelectExpandClause = selectExpandClause;
  332. Mock<ODataSerializer> serializer = new Mock<ODataSerializer>(ODataPayloadKind.Property);
  333. Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
  334. serializerProvider.Setup(p => p.GetODataPayloadSerializer(model, typeof(int), request)).Returns(serializer.Object);
  335. serializer
  336. .Setup(s => s.WriteObject(42, typeof(int), It.IsAny<ODataMessageWriter>(),
  337. It.Is<ODataSerializerContext>(c => c.SelectExpandClause == selectExpandClause)))
  338. .Verifiable();
  339. ODataDeserializerProvider deserializerProvider = new DefaultODataDeserializerProvider();
  340. var formatter = new ODataMediaTypeFormatter(deserializerProvider, serializerProvider.Object, Enumerable.Empty<ODataPayloadKind>());
  341. formatter.Request = request;
  342. HttpContent content = new StringContent("42");
  343. content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata.metadata=full");
  344. // Act
  345. formatter.WriteToStreamAsync(typeof(int), 42, new MemoryStream(), content, transportContext: null);
  346. // Assert
  347. serializer.Verify();
  348. }
  349. [Fact]
  350. public void MessageReaderSettings_Property()
  351. {
  352. var formatter = CreateFormatter();
  353. Assert.NotNull(formatter.MessageReaderSettings);
  354. Assert.True(formatter.MessageReaderSettings.DisableMessageStreamDisposal);
  355. }
  356. [Fact]
  357. public void MessageWriterSettings_Property()
  358. {
  359. var formatter = CreateFormatter();
  360. Assert.NotNull(formatter.MessageWriterSettings);
  361. Assert.True(formatter.MessageWriterSettings.DisableMessageStreamDisposal);
  362. Assert.True(formatter.MessageWriterSettings.AutoComputePayloadMetadataInJson);
  363. }
  364. [Fact]
  365. public void MessageReaderQuotas_Property_RoundTrip()
  366. {
  367. var formatter = CreateFormatter();
  368. formatter.MessageReaderQuotas.MaxNestingDepth = 42;
  369. Assert.Equal(42, formatter.MessageReaderQuotas.MaxNestingDepth);
  370. }
  371. [Fact]
  372. public void MessageWriterQuotas_Property_RoundTrip()
  373. {
  374. var formatter = CreateFormatter();
  375. formatter.MessageWriterQuotas.MaxNestingDepth = 42;
  376. Assert.Equal(42, formatter.MessageWriterQuotas.MaxNestingDepth);
  377. }
  378. [Fact]
  379. public void Default_ReceiveMessageSize_Is_MaxedOut()
  380. {
  381. var formatter = CreateFormatter();
  382. Assert.Equal(Int64.MaxValue, formatter.MessageReaderQuotas.MaxReceivedMessageSize);
  383. }
  384. [Fact]
  385. public void MessageReaderQuotas_Is_Passed_To_ODataLib()
  386. {
  387. ODataMediaTypeFormatter formatter = CreateFormatter();
  388. formatter.MessageReaderSettings.MessageQuotas.MaxReceivedMessageSize = 1;
  389. HttpContent content = new StringContent("{ 'Number' : '42' }");
  390. content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
  391. Assert.Throws<ODataException>(
  392. () => formatter.ReadFromStreamAsync(typeof(int), content.ReadAsStreamAsync().Result, content, formatterLogger: null).Result,
  393. "The maximum number of bytes allowed to be read from the stream has been exceeded. After the last read operation, a total of 19 bytes has been read from the stream; however a maximum of 1 bytes is allowed.");
  394. }
  395. [Fact]
  396. public void Request_IsPassedThroughDeserializerContext()
  397. {
  398. // Arrange
  399. var model = CreateModel();
  400. var request = CreateFakeODataRequest(model);
  401. Mock<ODataEdmTypeDeserializer> deserializer = new Mock<ODataEdmTypeDeserializer>(ODataPayloadKind.Property);
  402. Mock<ODataDeserializerProvider> deserializerProvider = new Mock<ODataDeserializerProvider>();
  403. deserializerProvider.Setup(p => p.GetEdmTypeDeserializer(It.IsAny<IEdmTypeReference>())).Returns(deserializer.Object);
  404. deserializer
  405. .Setup(d => d.Read(It.IsAny<ODataMessageReader>(), typeof(int), It.Is<ODataDeserializerContext>(c => c.Request == request)))
  406. .Verifiable();
  407. ODataSerializerProvider serializerProvider = new DefaultODataSerializerProvider();
  408. var formatter = new ODataMediaTypeFormatter(deserializerProvider.Object, serializerProvider, Enumerable.Empty<ODataPayloadKind>());
  409. formatter.Request = request;
  410. HttpContent content = new StringContent("42");
  411. content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata.metadata=full");
  412. // Act
  413. formatter.ReadFromStreamAsync(typeof(int), new MemoryStream(), content, formatterLogger: null);
  414. // Assert
  415. deserializer.Verify();
  416. }
  417. public static TheoryDataSet<ODataPath, ODataPayloadKind> CanReadTypeTypesTestData
  418. {
  419. get
  420. {
  421. CustomersModelWithInheritance model = new CustomersModelWithInheritance();
  422. EntitySetPathSegment entitySetSegment = new EntitySetPathSegment(model.Customers);
  423. KeyValuePathSegment keyValueSegment = new KeyValuePathSegment("42");
  424. NavigationPathSegment navSegment = new NavigationPathSegment(model.Customer.FindProperty("Orders") as IEdmNavigationProperty);
  425. PropertyAccessPathSegment propertySegment = new PropertyAccessPathSegment(model.Customer.FindProperty("Address") as IEdmStructuralProperty);
  426. return new TheoryDataSet<ODataPath, ODataPayloadKind>
  427. {
  428. { new ODataPath(entitySetSegment), ODataPayloadKind.Entry }, // POST ~/entityset
  429. { new ODataPath(entitySetSegment, keyValueSegment), ODataPayloadKind.Entry }, // PUT ~/entityset(key)
  430. { new ODataPath(entitySetSegment, keyValueSegment, navSegment), ODataPayloadKind.Entry }, // PUT ~/entityset(key)/nav
  431. { new ODataPath(entitySetSegment, keyValueSegment, propertySegment), ODataPayloadKind.Property }
  432. };
  433. }
  434. }
  435. [Theory]
  436. [PropertyData("CanReadTypeTypesTestData")]
  437. public void CanReadType_ForTypeless_ReturnsExpectedResult_DependingOnODataPathAndPayloadKind(ODataPath path, ODataPayloadKind payloadKind)
  438. {
  439. // Arrange
  440. IEnumerable<ODataPayloadKind> allPayloadKinds = Enum.GetValues(typeof(ODataPayloadKind)).Cast<ODataPayloadKind>();
  441. var model = CreateModel();
  442. var request = CreateFakeODataRequest(model);
  443. request.ODataProperties().Path = path;
  444. var formatterWithGivenPayload = new ODataMediaTypeFormatter(new[] { payloadKind }) { Request = request };
  445. var formatterWithoutGivenPayload = new ODataMediaTypeFormatter(allPayloadKinds.Except(new[] { payloadKind })) { Request = request };
  446. // Act & Assert
  447. Assert.True(formatterWithGivenPayload.CanReadType(typeof(IEdmObject)));
  448. Assert.False(formatterWithoutGivenPayload.CanReadType(typeof(IEdmObject)));
  449. }
  450. public static TheoryDataSet<ODataPayloadKind, Type> CanWriteType_ReturnsExpectedResult_ForEdmObjects_TestData
  451. {
  452. get
  453. {
  454. Type entityCollectionEdmObjectType = new Mock<IEdmObject>().As<IEnumerable<IEdmEntityObject>>().Object.GetType();
  455. Type complexCollectionEdmObjectType = new Mock<IEdmObject>().As<IEnumerable<IEdmComplexObject>>().Object.GetType();
  456. return new TheoryDataSet<ODataPayloadKind, Type>
  457. {
  458. { ODataPayloadKind.Entry , typeof(IEdmEntityObject) },
  459. { ODataPayloadKind.Entry , typeof(TypedEdmEntityObject) },
  460. { ODataPayloadKind.Feed , entityCollectionEdmObjectType },
  461. { ODataPayloadKind.Feed , typeof(IEnumerable<IEdmEntityObject>) },
  462. { ODataPayloadKind.Property , typeof(IEdmComplexObject) },
  463. { ODataPayloadKind.Property , typeof(TypedEdmComplexObject) },
  464. { ODataPayloadKind.Collection , complexCollectionEdmObjectType },
  465. { ODataPayloadKind.Collection , typeof(IEnumerable<IEdmComplexObject>) },
  466. { ODataPayloadKind.Property, typeof(NullEdmComplexObject) }
  467. };
  468. }
  469. }
  470. [Theory]
  471. [PropertyData("CanWriteType_ReturnsExpectedResult_ForEdmObjects_TestData")]
  472. public void CanWriteType_ReturnsTrueForEdmObjects_WithRightPayload(ODataPayloadKind payloadKind, Type type)
  473. {
  474. // Arrange
  475. IEnumerable<ODataPayloadKind> allPayloadKinds = Enum.GetValues(typeof(ODataPayloadKind)).Cast<ODataPayloadKind>();
  476. var model = CreateModel();
  477. var request = CreateFakeODataRequest(model);
  478. var formatterWithGivenPayload = new ODataMediaTypeFormatter(new[] { payloadKind }) { Request = request };
  479. var formatterWithoutGivenPayload = new ODataMediaTypeFormatter(allPayloadKinds.Except(new[] { payloadKind })) { Request = request };
  480. // Act & Assert
  481. Assert.True(formatterWithGivenPayload.CanWriteType(type));
  482. Assert.False(formatterWithoutGivenPayload.CanWriteType(type));
  483. }
  484. public static TheoryDataSet<Type> InvalidIEdmObjectImplementationTypes
  485. {
  486. get
  487. {
  488. return new TheoryDataSet<Type>
  489. {
  490. typeof(IEdmObject),
  491. typeof(TypedEdmStructuredObject),
  492. new Mock<IEdmObject>().Object.GetType(),
  493. new Mock<IEdmObject>().As<IEnumerable<IEdmObject>>().Object.GetType()
  494. };
  495. }
  496. }
  497. [Theory]
  498. [PropertyData("InvalidIEdmObjectImplementationTypes")]
  499. public void CanWriteType_ReturnsFalse_ForInvalidIEdmObjectImplementations_NoMatterThePayload(Type type)
  500. {
  501. var model = CreateModel();
  502. var request = CreateFakeODataRequest(model);
  503. IEnumerable<ODataPayloadKind> allPayloadKinds = Enum.GetValues(typeof(ODataPayloadKind)).Cast<ODataPayloadKind>();
  504. var formatter = new ODataMediaTypeFormatter(allPayloadKinds);
  505. formatter.Request = request;
  506. var result = formatter.CanWriteType(type);
  507. Assert.False(result);
  508. }
  509. [Fact]
  510. public void WriteToStreamAsync_ThrowsSerializationException_IfEdmTypeIsNull()
  511. {
  512. var model = CreateModel();
  513. var request = CreateFakeODataRequest(model);
  514. var formatter = new ODataMediaTypeFormatter(new ODataPayloadKind[0]);
  515. formatter.Request = request;
  516. Mock<IEdmObject> edmObject = new Mock<IEdmObject>();
  517. Assert.Throws<SerializationException>(
  518. () => formatter
  519. .WriteToStreamAsync(typeof(int), edmObject.Object, new MemoryStream(), new Mock<HttpContent>().Object, transportContext: null)
  520. .Wait(),
  521. "The EDM type of the object of type 'Castle.Proxies.IEdmObjectProxy' is null. The EDM type of an IEdmObject cannot be null.");
  522. }
  523. [Fact]
  524. public void WriteToStreamAsync_UsesTheRightEdmSerializer_ForEdmObjects()
  525. {
  526. // Arrange
  527. IEdmEntityTypeReference edmType = new EdmEntityTypeReference(new EdmEntityType("NS", "Name"), isNullable: false);
  528. var model = CreateModel();
  529. var request = CreateFakeODataRequest(model);
  530. Mock<IEdmObject> instance = new Mock<IEdmObject>();
  531. instance.Setup(e => e.GetEdmType()).Returns(edmType);
  532. Mock<ODataEdmTypeSerializer> serializer = new Mock<ODataEdmTypeSerializer>(ODataPayloadKind.Entry);
  533. serializer
  534. .Setup(s => s.WriteObject(instance.Object, instance.GetType(), It.IsAny<ODataMessageWriter>(), It.IsAny<ODataSerializerContext>()))
  535. .Verifiable();
  536. Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
  537. serializerProvider.Setup(s => s.GetEdmTypeSerializer(edmType)).Returns(serializer.Object);
  538. var formatter = new ODataMediaTypeFormatter(new DefaultODataDeserializerProvider(), serializerProvider.Object, new ODataPayloadKind[0]);
  539. formatter.Request = request;
  540. // Act
  541. formatter
  542. .WriteToStreamAsync(instance.GetType(), instance.Object, new MemoryStream(), new StreamContent(new MemoryStream()), transportContext: null)
  543. .Wait();
  544. // Assert
  545. serializer.Verify();
  546. }
  547. [Theory]
  548. [InlineData(typeof(SingleResult), false)]
  549. [InlineData(typeof(SingleResult<SampleType>), true)]
  550. [InlineData(typeof(SingleResult<TypeNotInModel>), false)]
  551. public void CanWriteType_ReturnsExpectedResult_ForSingleResult(Type type, bool expectedCanWriteTypeResult)
  552. {
  553. IEdmModel model = CreateModel();
  554. HttpRequestMessage request = CreateFakeODataRequest(model);
  555. ODataMediaTypeFormatter formatter = CreateFormatter(model, request, ODataPayloadKind.Entry);
  556. Assert.Equal(expectedCanWriteTypeResult, formatter.CanWriteType(type));
  557. }
  558. [Fact]
  559. public void WriteToStreamAsync_SetsMetadataUriWithSelectClause_OnODataWriterSettings()
  560. {
  561. // Arrange
  562. MemoryStream stream = new MemoryStream();
  563. StreamContent content = new StreamContent(stream);
  564. content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
  565. IEdmModel model = CreateModel();
  566. IEdmSchemaType entityType = model.FindDeclaredType("System.Net.Http.Formatting.SampleType");
  567. IEdmStructuralProperty property =
  568. ((IEdmStructuredType)entityType).FindProperty("Number") as IEdmStructuralProperty;
  569. HttpRequestMessage request = CreateFakeODataRequest(model);
  570. request.RequestUri = new Uri("http://localhost/sampleTypes?$select=Number");
  571. request.ODataProperties().SelectExpandClause =
  572. new SelectExpandClause(
  573. new Collection<SelectItem>
  574. {
  575. new PathSelectItem(new ODataSelectPath(new PropertySegment(property))),
  576. },
  577. allSelected: false);
  578. ODataMediaTypeFormatter formatter = CreateFormatter(model, request, ODataPayloadKind.Entry);
  579. // Act
  580. formatter.WriteToStreamAsync(typeof(SampleType[]), new SampleType[0], stream, content, transportContext: null);
  581. // Assert
  582. stream.Seek(0, SeekOrigin.Begin);
  583. string result = content.ReadAsStringAsync().Result;
  584. JObject obj = JObject.Parse(result);
  585. Assert.Equal("http://localhost/$metadata#sampleTypes(Number)", obj["@odata.context"]);
  586. }
  587. [Fact]
  588. public void ReadFromStreamAsync_UsesRightDeserializerFrom_ODataDeserializerProvider()
  589. {
  590. // Arrange
  591. MemoryStream stream = new MemoryStream();
  592. StringContent content = new StringContent("42");
  593. content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
  594. IEdmModel model = CreateModel();
  595. HttpRequestMessage request = CreateFakeODataRequest(model);
  596. Mock<ODataDeserializer> deserializer = new Mock<ODataDeserializer>(ODataPayloadKind.Property);
  597. deserializer.Setup(d => d.Read(It.IsAny<ODataMessageReader>(), typeof(int), It.IsAny<ODataDeserializerContext>()))
  598. .Verifiable();
  599. Mock<ODataDeserializerProvider> provider = new Mock<ODataDeserializerProvider>();
  600. provider.Setup(p => p.GetODataDeserializer(model, typeof(int), request)).Returns(deserializer.Object);
  601. // Act
  602. ODataMediaTypeFormatter formatter = new ODataMediaTypeFormatter(provider.Object,
  603. new DefaultODataSerializerProvider(), Enumerable.Empty<ODataPayloadKind>());
  604. formatter.Request = request;
  605. formatter.ReadFromStreamAsync(typeof(int), stream, content, null);
  606. // Assert
  607. deserializer.Verify();
  608. }
  609. private static Encoding CreateEncoding(string name)
  610. {
  611. if (name == "utf-8")
  612. {
  613. return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
  614. }
  615. else if (name == "utf-16")
  616. {
  617. return new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true);
  618. }
  619. else
  620. {
  621. throw new ArgumentException("name");
  622. }
  623. }
  624. private static string CreateFormattedContent(string value)
  625. {
  626. return string.Format(CultureInfo.InvariantCulture,
  627. "{{\r\n \"@odata.context\":\"http://dummy/$metadata#Edm.String\",\"value\":\"{0}\"\r\n}}", value);
  628. }
  629. protected override ODataMediaTypeFormatter CreateFormatter()
  630. {
  631. return CreateFormatterWithRequest();
  632. }
  633. protected override Mock<ODataMediaTypeFormatter> CreateMockFormatter()
  634. {
  635. var model = CreateModel();
  636. var request = CreateFakeODataRequest(model);
  637. ODataPayloadKind[] payloadKinds = new ODataPayloadKind[] { ODataPayloadKind.Property };
  638. var formatter = new Mock<ODataMediaTypeFormatter>(payloadKinds) { CallBase = true };
  639. formatter.Object.Request = request;
  640. return formatter;
  641. }
  642. protected override MediaTypeHeaderValue CreateSupportedMediaType()
  643. {
  644. return MediaTypeHeaderValue.Parse("application/json;odata.metadata=full");
  645. }
  646. private static ODataMediaTypeFormatter CreateFormatter(IEdmModel model)
  647. {
  648. return new ODataMediaTypeFormatter(new ODataPayloadKind[0]);
  649. }
  650. private static ODataMediaTypeFormatter CreateFormatter(IEdmModel model, HttpRequestMessage request,
  651. params ODataPayloadKind[] payloadKinds)
  652. {
  653. return new ODataMediaTypeFormatter(payloadKinds) { Request = request };
  654. }
  655. private static ODataMediaTypeFormatter CreateFormatterWithoutRequest()
  656. {
  657. return CreateFormatter(CreateModel());
  658. }
  659. private static ODataMediaTypeFormatter CreateFormatterWithJson(IEdmModel model, HttpRequestMessage request,
  660. params ODataPayloadKind[] payloadKinds)
  661. {
  662. ODataMediaTypeFormatter formatter = CreateFormatter(model, request, payloadKinds);
  663. formatter.SupportedMediaTypes.Add(ODataMediaTypes.ApplicationJsonODataMinimalMetadata);
  664. return formatter;
  665. }
  666. private static ODataMediaTypeFormatter CreateFormatterWithRequest()
  667. {
  668. var model = CreateModel();
  669. var request = CreateFakeODataRequest(model);
  670. return CreateFormatter(model, request);
  671. }
  672. private static HttpRequestMessage CreateFakeODataRequest(IEdmModel model)
  673. {
  674. var request = new HttpRequestMessage(HttpMethod.Get, "http://dummy/");
  675. request.ODataProperties().Model = model;
  676. HttpConfiguration configuration = new HttpConfiguration();
  677. configuration.Routes.MapFakeODataRoute();
  678. request.SetConfiguration(configuration);
  679. request.ODataProperties().Path =
  680. new ODataPath(new EntitySetPathSegment(model.EntityContainer.EntitySets().Single()));
  681. request.SetFakeODataRouteName();
  682. return request;
  683. }
  684. private static IEdmModel CreateModel()
  685. {
  686. ODataConventionModelBuilder model = ODataModelBuilderMocks.GetModelBuilderMock<ODataConventionModelBuilder>();
  687. model.ModelAliasingEnabled = false;
  688. model.EntityType<SampleType>();
  689. model.EntitySet<SampleType>("sampleTypes");
  690. return model.GetEdmModel();
  691. }
  692. public override IEnumerable<MediaTypeHeaderValue> ExpectedSupportedMediaTypes
  693. {
  694. get
  695. {
  696. return new MediaTypeHeaderValue[0];
  697. }
  698. }
  699. public override IEnumerable<Encoding> ExpectedSupportedEncodings
  700. {
  701. get
  702. {
  703. return new Encoding[0];
  704. }
  705. }
  706. public override byte[] ExpectedSampleTypeByteRepresentation
  707. {
  708. get
  709. {
  710. return Encoding.UTF8.GetBytes(
  711. "{" +
  712. "\"@odata.context\":\"http://localhost/$metadata#sampleTypes/$entity\"," +
  713. "\"@odata.type\":\"#System.Net.Http.Formatting.SampleType\"," +
  714. "\"@odata.id\":\"http://localhost/sampleTypes(42)\"," +
  715. "\"@odata.editLink\":\"http://localhost/sampleTypes(42)\"," +
  716. "\"Number\":42" +
  717. "}");
  718. }
  719. }
  720. private class TypeNotInModel
  721. {
  722. }
  723. }
  724. }