using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Reflection; using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; using AMWD.Net.Api.Cloudflare; using AMWD.Net.Api.Cloudflare.Auth; using Moq; using Moq.Protected; namespace Cloudflare.Core.Tests.CloudflareClientTests { [TestClass] public class PutAsyncTest { private const string _baseUrl = "https://localhost/api/v4/"; private HttpMessageHandlerMock _httpHandlerMock; private Mock _clientOptionsMock; private Mock _authenticationMock; private TestClass _request; [TestInitialize] public void Initialize() { _httpHandlerMock = new HttpMessageHandlerMock(); _authenticationMock = new Mock(); _clientOptionsMock = new Mock(); _authenticationMock .Setup(a => a.AddHeader(It.IsAny())) .Callback(c => c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Some-API-Token")); _clientOptionsMock.Setup(o => o.BaseUrl).Returns(_baseUrl); _clientOptionsMock.Setup(o => o.Timeout).Returns(TimeSpan.FromSeconds(60)); _clientOptionsMock.Setup(o => o.MaxRetries).Returns(2); _clientOptionsMock.Setup(o => o.DefaultHeaders).Returns(new Dictionary()); _clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary()); _clientOptionsMock.Setup(o => o.AllowRedirects).Returns(false); _clientOptionsMock.Setup(o => o.UseProxy).Returns(false); _request = new TestClass { Int = 54321, Str = "Happy Testing!" }; } [TestMethod] [ExpectedException(typeof(ObjectDisposedException))] public async Task ShouldThrowDisposed() { // Arrange var client = GetClient() as CloudflareClient; client.Dispose(); // Act await client.PutAsync("test", _request); // Assert - ObjectDisposedException } [DataTestMethod] [DataRow(null)] [DataRow("")] [DataRow(" ")] [ExpectedException(typeof(ArgumentNullException))] public async Task ShouldThrowArgumentNullOnRequestPath(string path) { // Arrange var client = GetClient(); // Act await client.PutAsync(path, _request); // Assert - ArgumentNullException } [TestMethod] [ExpectedException(typeof(ArgumentException))] public async Task ShouldThrowArgumentOnRequestPath() { // Arrange var client = GetClient(); // Act await client.PutAsync("foo?bar=baz", _request); // Assert - ArgumentException } [TestMethod] public async Task ShouldPut() { // Arrange _httpHandlerMock.Responses.Enqueue(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json), }); var client = GetClient(); // Act var response = await client.PutAsync("test", _request); // Assert Assert.IsNotNull(response); Assert.IsTrue(response.Success); Assert.IsNotNull(response.Errors); Assert.IsNotNull(response.Messages); Assert.IsNull(response.ResultInfo); Assert.AreEqual(0, response.Errors.Count); Assert.AreEqual(0, response.Messages.Count); Assert.IsNotNull(response.Result); Assert.AreEqual("some-string", response.Result.Str); Assert.AreEqual(123, response.Result.Int); Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count); var callback = _httpHandlerMock.Callbacks.First(); Assert.AreEqual(HttpMethod.Put, callback.Method); Assert.AreEqual("https://localhost/api/v4/test", callback.Url); Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content); Assert.AreEqual(3, callback.Headers.Count); Assert.IsTrue(callback.Headers.ContainsKey("Accept")); Assert.IsTrue(callback.Headers.ContainsKey("Authorization")); Assert.IsTrue(callback.Headers.ContainsKey("User-Agent")); Assert.AreEqual("application/json", callback.Headers["Accept"]); Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]); Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]); _httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); VerifyDefaults(); VerifyNoOtherCalls(); } [TestMethod] public async Task ShouldPutHttpContentDirectly() { // Arrange var stringContent = new StringContent(@"{""test"":""HERE ?""}", Encoding.UTF8, "application/json"); _httpHandlerMock.Responses.Enqueue(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(@"{""success"": true, ""errors"": [], ""messages"": [], ""result"": { ""string"": ""some-string"", ""integer"": 123 }}", Encoding.UTF8, MediaTypeNames.Application.Json), }); var client = GetClient(); // Act var response = await client.PutAsync("test", stringContent); // Assert Assert.IsNotNull(response); Assert.IsTrue(response.Success); Assert.IsNotNull(response.Errors); Assert.IsNotNull(response.Messages); Assert.IsNull(response.ResultInfo); Assert.AreEqual(0, response.Errors.Count); Assert.AreEqual(0, response.Messages.Count); Assert.IsNotNull(response.Result); Assert.AreEqual("some-string", response.Result.Str); Assert.AreEqual(123, response.Result.Int); Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count); var callback = _httpHandlerMock.Callbacks.First(); Assert.AreEqual(HttpMethod.Put, callback.Method); Assert.AreEqual("https://localhost/api/v4/test", callback.Url); Assert.AreEqual(@"{""test"":""HERE ?""}", callback.Content); Assert.AreEqual(3, callback.Headers.Count); Assert.IsTrue(callback.Headers.ContainsKey("Accept")); Assert.IsTrue(callback.Headers.ContainsKey("Authorization")); Assert.IsTrue(callback.Headers.ContainsKey("User-Agent")); Assert.AreEqual("application/json", callback.Headers["Accept"]); Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]); Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]); _httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); VerifyDefaults(); VerifyNoOtherCalls(); } [DataTestMethod] [DataRow(HttpStatusCode.Unauthorized)] [DataRow(HttpStatusCode.Forbidden)] public async Task ShouldThrowAuthenticationExceptionOnStatusCode(HttpStatusCode statusCode) { // Arrange _httpHandlerMock.Responses.Enqueue(new HttpResponseMessage { StatusCode = statusCode, Content = new StringContent(@"{""success"": false, ""errors"": [{ ""code"": ""4711"", ""message"": ""foo & baz."" }, { ""code"": ""4712"", ""message"": ""Happy Error!"" }], ""messages"": []}", Encoding.UTF8, MediaTypeNames.Application.Json), }); var client = GetClient(); try { // Act await client.PutAsync("foo", _request); Assert.Fail(); } catch (AuthenticationException ex) { // Assert Assert.IsNull(ex.InnerException); Assert.AreEqual($"4711: foo & baz.{Environment.NewLine}4712: Happy Error!", ex.Message); } } [TestMethod] public async Task ShouldReturnPlainText() { // Arrange _clientOptionsMock.Setup(o => o.DefaultQueryParams).Returns(new Dictionary { { "bar", "08/15" } }); _httpHandlerMock.Responses.Enqueue(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent("This is an awesome text ;-)", Encoding.UTF8, MediaTypeNames.Text.Plain), }); var client = GetClient(); // Act var response = await client.PutAsync("some-awesome-path", _request); // Assert Assert.IsNotNull(response); Assert.IsTrue(response.Success); Assert.IsNotNull(response.Errors); Assert.IsNotNull(response.Messages); Assert.IsNotNull(response.ResultInfo); Assert.AreEqual(0, response.Errors.Count); Assert.AreEqual(0, response.Messages.Count); Assert.AreEqual(0, response.ResultInfo.Count); Assert.AreEqual(0, response.ResultInfo.Page); Assert.AreEqual(0, response.ResultInfo.PerPage); Assert.AreEqual(0, response.ResultInfo.TotalCount); Assert.AreEqual("This is an awesome text ;-)", response.Result); Assert.AreEqual(1, _httpHandlerMock.Callbacks.Count); var callback = _httpHandlerMock.Callbacks.First(); Assert.AreEqual(HttpMethod.Put, callback.Method); Assert.AreEqual("https://localhost/api/v4/some-awesome-path?bar=08%2F15", callback.Url); Assert.AreEqual(@"{""string"":""Happy Testing!"",""integer"":54321}", callback.Content); Assert.AreEqual(3, callback.Headers.Count); Assert.IsTrue(callback.Headers.ContainsKey("Accept")); Assert.IsTrue(callback.Headers.ContainsKey("Authorization")); Assert.IsTrue(callback.Headers.ContainsKey("User-Agent")); Assert.AreEqual("application/json", callback.Headers["Accept"]); Assert.AreEqual("Bearer Some-API-Token", callback.Headers["Authorization"]); Assert.AreEqual("AMWD.CloudflareClient/1.0.0", callback.Headers["User-Agent"]); _httpHandlerMock.Mock.Protected().Verify("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); _authenticationMock.Verify(m => m.AddHeader(It.IsAny()), Times.Once); _clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once); _clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once); _clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.Proxy, Times.Once); VerifyNoOtherCalls(); } [TestMethod] [ExpectedException(typeof(JsonReaderException))] public async Task ShouldThrowExceptionOnInvalidResponse() { // Arrange _httpHandlerMock.Responses.Enqueue(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent("This is a bad text :p", Encoding.UTF8, MediaTypeNames.Text.Plain), }); var client = GetClient(); // Act await client.PutAsync("some-path", _request); } private void VerifyDefaults() { _authenticationMock.Verify(m => m.AddHeader(It.IsAny()), Times.Once); _clientOptionsMock.Verify(o => o.BaseUrl, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.Timeout, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.MaxRetries, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.DefaultHeaders, Times.Once); _clientOptionsMock.Verify(o => o.DefaultQueryParams, Times.Once); _clientOptionsMock.Verify(o => o.AllowRedirects, Times.Once); _clientOptionsMock.Verify(o => o.UseProxy, Times.Exactly(2)); _clientOptionsMock.Verify(o => o.Proxy, Times.Once); } private void VerifyNoOtherCalls() { _httpHandlerMock.Mock.VerifyNoOtherCalls(); _authenticationMock.VerifyNoOtherCalls(); _clientOptionsMock.VerifyNoOtherCalls(); } private ICloudflareClient GetClient() { var httpClient = new HttpClient(_httpHandlerMock.Mock.Object) { BaseAddress = new Uri(_baseUrl), Timeout = _clientOptionsMock.Object.Timeout, }; httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", "1.0.0")); httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); if (_clientOptionsMock.Object.DefaultHeaders.Count > 0) { foreach (var headerKvp in _clientOptionsMock.Object.DefaultHeaders) httpClient.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value); } _authenticationMock.Object.AddHeader(httpClient); _authenticationMock.Invocations.Clear(); _clientOptionsMock.Invocations.Clear(); var client = new CloudflareClient(_authenticationMock.Object, _clientOptionsMock.Object); var httpClientField = client.GetType() .GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance); (httpClientField.GetValue(client) as HttpClient).Dispose(); httpClientField.SetValue(client, httpClient); return client; } private class TestClass { [JsonProperty("string")] public string Str { get; set; } [JsonProperty("integer")] public int Int { get; set; } } private class TestFilter : IQueryParameterFilter { public IDictionary GetQueryParameters() { return new Dictionary { { "test", "filter-text" } }; } } } }