google-api-dotnet-client / Src /Support /Google.Apis.Tests /Apis /Requests /ClientServiceRequestTest.cs
| /* | |
| Copyright 2011 Google Inc | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| using Google.Apis.Discovery; | |
| using Google.Apis.Http; | |
| using Google.Apis.Requests; | |
| using Google.Apis.Services; | |
| using Google.Apis.Tests.Mocks; | |
| using Google.Apis.Util; | |
| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.IO.Compression; | |
| using System.Linq; | |
| using System.Net; | |
| using System.Net.Http; | |
| using System.Net.Http.Headers; | |
| using System.Text; | |
| using System.Threading; | |
| using System.Threading.Tasks; | |
| using Xunit; | |
| namespace Google.Apis.Tests.Apis.Requests | |
| { | |
| /// <summary>Tests for the <seealso cref="Google.Apis.Requests.ClientServiceRequest"/>.</summary> | |
| public class ClientServiceRequestTest | |
| { | |
| /// <summary>Helper method to get a string from the stream.</summary> | |
| private static string ExtractStringFromStream(Stream stream) | |
| { | |
| var buffer = new byte[1000]; | |
| var len = stream.Read(buffer, 0, 1000); | |
| return Encoding.UTF8.GetString(buffer, 0, len); | |
| } | |
| /// <summary>A mock response class.</summary> | |
| class MockResponse : IDirectResponseSchema | |
| { | |
| [] | |
| public string ETag { get; set; } | |
| [] | |
| public string Name { get; set; } | |
| [] | |
| public int Id { get; set; } | |
| public override bool Equals(object obj) | |
| { | |
| var other = obj as MockResponse; | |
| return (other != null && other.ETag == ETag && other.Name == Name && other.Id == Id); | |
| } | |
| public override int GetHashCode() | |
| { | |
| return (ETag ?? string.Empty).GetHashCode() ^ (Name ?? string.Empty).GetHashCode() + Id; | |
| } | |
| } | |
| /// <summary>A mock request class.</summary> | |
| class MockRequest : IDirectResponseSchema | |
| { | |
| [] | |
| public string ETag { get; set; } | |
| [] | |
| public string Name { get; set; } | |
| public override bool Equals(object obj) | |
| { | |
| var other = obj as MockRequest; | |
| return (other != null && other.ETag == ETag && other.Name == Name); | |
| } | |
| public override int GetHashCode() | |
| { | |
| return (ETag ?? string.Empty).GetHashCode() ^ (Name ?? string.Empty).GetHashCode(); | |
| } | |
| } | |
| /// <summary>A mock service request which returns <see cref="MockResponse"/>.</summary> | |
| class TestClientServiceRequest : ClientServiceRequest<MockResponse> | |
| { | |
| public string OverrideRestPath { get; set; } | |
| /// <summary>Gets or sets a request number. It's used on concurrent tests.</summary> | |
| public int CallNum { get; set; } | |
| private string httpMethod; | |
| private object body; | |
| public TestClientServiceRequest(IClientService service, string httpMethod, object body) | |
| : base(service) | |
| { | |
| this.httpMethod = httpMethod; | |
| this.body = body; | |
| InitParameters(); | |
| RequestParameters.Add("id", new Parameter | |
| { | |
| Name = "id", | |
| ParameterType = "path" | |
| }); | |
| } | |
| // Normally requests override the ApiVersion with a constant (if at all) but it's | |
| // convenient for us to be able to do it on mutable basis for tests. | |
| public override string ApiVersion => ApiVersionImpl; | |
| public string ApiVersionImpl { get; set; } | |
| public override string MethodName => httpMethod; | |
| public override string RestPath => OverrideRestPath ?? "restPath" + CallNum; | |
| public override string HttpMethod => httpMethod; | |
| protected override object GetBody() => body; | |
| [] | |
| public string Id { get; set; } | |
| public string GenerateRequestUriPublic() => GenerateRequestUri(); | |
| } | |
| /// <summary>A mock message handler which returns an error.</summary> | |
| class ErrorMessageHandler : CountableMessageHandler | |
| { | |
| public const string ExpectedError = | |
| @"Message[Login Required] Location[Authorization - header] Reason[required] Domain[global]"; | |
| public const string ErrorContent = | |
| @"{ | |
| ""error"": { | |
| ""errors"": [ | |
| { | |
| ""domain"": ""global"", | |
| ""reason"": ""required"", | |
| ""message"": ""Login Required"", | |
| ""locationType"": ""header"", | |
| ""location"": ""Authorization"" | |
| } | |
| ], | |
| ""code"": 401, | |
| ""message"": ""Login Required"" | |
| } | |
| }"; | |
| public const string ExpectedExceptionMessage = "The service TestService has thrown an exception. HttpStatusCode is Unauthorized. Login Required"; | |
| protected override Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, | |
| CancellationToken cancellationToken) | |
| { | |
| var response = new HttpResponseMessage | |
| { | |
| Content = new StringContent(ErrorContent), | |
| StatusCode = HttpStatusCode.Unauthorized | |
| }; | |
| TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(); | |
| tcs.SetResult(response); | |
| return tcs.Task; | |
| } | |
| } | |
| /// <summary>Tests message handler which tests the content on the request and the response.</summary> | |
| class TestBodyMessageHandler : CountableMessageHandler | |
| { | |
| /// <summary>Gets or sets indication is GZip is eanbled.</summary> | |
| public bool GZipEnabled { get; set; } | |
| /// <summary>Gets or sets the expected request object.</summary> | |
| public MockRequest ExpectedRequestObject { get; set; } | |
| /// <summary>Gets or sets the returned response object </summary> | |
| public MockResponse ResponseObject { get; set; } | |
| /// <summary>Gets or sets the Serializer which is used to serialize and deserialize messages.</summary> | |
| public ISerializer Serializer { get; set; } | |
| public string ResponseETag = "\"some-etag-here\""; | |
| protected override async Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, | |
| CancellationToken cancellationToken) | |
| { | |
| var mediaType = "application/json"; | |
| string strObject = null; | |
| // If gzip enabled the request content is a gzip stream, otherwise it's a string content. | |
| if (GZipEnabled) | |
| { | |
| Assert.IsAssignableFrom<StreamContent>(request.Content); | |
| var encoding = request.Content.Headers.ContentEncoding; | |
| Assert.True(encoding.Count == 1 && encoding.First().Equals("gzip"), | |
| "Content-Encoding should be 'gzip'"); | |
| var stream = await request.Content.ReadAsStreamAsync().ConfigureAwait(false); | |
| using (GZipStream gzipStream = new GZipStream(stream, CompressionMode.Decompress)) | |
| { | |
| strObject = ExtractStringFromStream(gzipStream); | |
| } | |
| } | |
| else | |
| { | |
| Assert.IsAssignableFrom<StringContent>(request.Content); | |
| strObject = await request.Content.ReadAsStringAsync().ConfigureAwait(false); | |
| } | |
| Assert.Equal(new MediaTypeHeaderValue(mediaType) | |
| { | |
| CharSet = Encoding.UTF8.WebName | |
| }, request.Content.Headers.ContentType); | |
| // Deserialize the requested object and check it's equal to the expected object. | |
| var obj = Serializer.Deserialize<MockRequest>(strObject); | |
| Assert.Equal(ExpectedRequestObject, obj); | |
| // Return the response (with ETag). | |
| var response = new HttpResponseMessage(); | |
| var serializedObject = Serializer.Serialize(ResponseObject); | |
| response.Content = new StringContent(serializedObject, Encoding.UTF8, mediaType); | |
| response.Headers.ETag = new EntityTagHeaderValue(ResponseETag); | |
| return response; | |
| } | |
| } | |
| /// <summary> | |
| /// A mock exception which is thrown from a mock message handler in case it is configured to throw exceptions. | |
| /// </summary> | |
| class InvalidOperationMockException : Exception | |
| { | |
| public InvalidOperationMockException(string str) | |
| : base(str) | |
| { | |
| } | |
| } | |
| /// <summary>A message handler which returns a HTTP response message or throw an exception.</summary> | |
| class MockMessageHandler : CountableMessageHandler | |
| { | |
| private bool ThrowException { get; set; } | |
| public MockMessageHandler(bool throwException = false) | |
| { | |
| ThrowException = throwException; | |
| } | |
| protected override Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, | |
| CancellationToken cancellationToken) | |
| { | |
| if (ThrowException) | |
| { | |
| throw new InvalidOperationMockException("INVALID"); | |
| } | |
| TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(); | |
| tcs.SetResult(new HttpResponseMessage()); | |
| return tcs.Task; | |
| } | |
| } | |
| /// <summary>A message handler which is used to cancel a HTTP request in the middle.</summary> | |
| class CancelRedirectMessageHandler : CountableMessageHandler | |
| { | |
| /// <summary>The cancellation token we are going to use to cancel a request.</summary> | |
| public CancellationTokenSource CancellationTokenSource { get; set; } | |
| /// <summary>The request index we are going to cancel.</summary> | |
| public int CancelRequestNum { get; set; } | |
| protected override Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, | |
| CancellationToken cancellationToken) | |
| { | |
| if (Calls == CancelRequestNum) | |
| { | |
| CancellationTokenSource.Cancel(); | |
| } | |
| var response = new HttpResponseMessage() | |
| { | |
| StatusCode = HttpStatusCode.Redirect, | |
| RequestMessage = request | |
| }; | |
| response.Headers.Location = new Uri("http://www.test.com"); | |
| TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(); | |
| tcs.SetResult(response); | |
| return tcs.Task; | |
| } | |
| } | |
| /// <summary> | |
| /// A message handler which checks concurrent calls (each odd request will succeeded, and even request will | |
| /// fail on the first try and will succeeded in the second try. | |
| /// </summary> | |
| class ConcurrentCallsHandler : CountableMessageHandler | |
| { | |
| /// <summary>Gets or sets the Serializer which is used to serialize and deserialize messages.</summary> | |
| public ISerializer Serializer { get; set; } | |
| protected override Task<HttpResponseMessage> SendAsyncCore(HttpRequestMessage request, | |
| CancellationToken cancellationToken) | |
| { | |
| var response = new HttpResponseMessage(); | |
| var uri = request.RequestUri.AbsoluteUri; | |
| int lastDigit = 0; | |
| if (int.TryParse(uri[uri.Length - 1].ToString(), out lastDigit) && lastDigit % 2 == 0) | |
| { | |
| response.StatusCode = System.Net.HttpStatusCode.ServiceUnavailable; | |
| request.RequestUri = new Uri(uri + (lastDigit + 1)); | |
| } | |
| else | |
| { | |
| var mockObject = new MockResponse { Name = "Name-" + lastDigit }; | |
| var serializedObject = Serializer.Serialize(mockObject); | |
| response.Content = new StringContent(serializedObject, Encoding.UTF8, "application/json"); | |
| } | |
| TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(); | |
| tcs.SetResult(response); | |
| return tcs.Task; | |
| } | |
| /// <summary>Unsuccessful response handler which "handles" service unavailable responses.</summary> | |
| internal class ServiceUnavailableUnsuccessfulResponseHandler : IHttpUnsuccessfulResponseHandler | |
| { | |
| public Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args) | |
| { | |
| TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(); | |
| tcs.SetResult(args.Response.StatusCode.Equals(HttpStatusCode.ServiceUnavailable)); | |
| return tcs.Task; | |
| } | |
| } | |
| /// <summary> | |
| /// Adds service unavailable unsuccessful response handler to the configurable message handler. | |
| /// </summary> | |
| internal class Initializer : IConfigurableHttpClientInitializer | |
| { | |
| public void Initialize(ConfigurableHttpClient httpClient) | |
| { | |
| httpClient.MessageHandler.AddUnsuccessfulResponseHandler( | |
| new ServiceUnavailableUnsuccessfulResponseHandler()); | |
| } | |
| } | |
| } | |
| /// <summary>Tests that canceling a outgoing request to the server works as expected.</summary> | |
| [] | |
| public async Task ExecuteAsync_Cancel() | |
| { | |
| await SubtestExecuteAsync_Cancel(1); | |
| await SubtestExecuteAsync_Cancel(5); | |
| await SubtestExecuteAsync_Cancel(10); | |
| await SubtestExecuteAsync_Cancel(11); | |
| } | |
| /// <summary> | |
| /// Test helper to test canceling token in a middle of a request to the server | |
| /// </summary> | |
| /// <param name="cancelRequestNum"> | |
| /// The index of the "server"'s request which a cancel token will be mimic. | |
| /// </param> | |
| private async Task SubtestExecuteAsync_Cancel(int cancelRequestNum) | |
| { | |
| var handler = new CancelRedirectMessageHandler(); | |
| handler.CancellationTokenSource = new CancellationTokenSource(); | |
| handler.CancelRequestNum = cancelRequestNum; | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| TestClientServiceRequest request; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| request = new TestClientServiceRequest(service, "POST", new MockRequest()); | |
| var exception = await Assert.ThrowsAnyAsync<Exception>(() => request.ExecuteAsync(handler.CancellationTokenSource.Token)); | |
| if (exception is OperationCanceledException) | |
| { | |
| // We expect a task canceled exception in case the canceled request is less or equal total | |
| // number of retries. | |
| Assert.False(cancelRequestNum > service.HttpClient.MessageHandler.NumRedirects + 1); | |
| } | |
| else | |
| { | |
| // Canceled exception wasn't thrown, in that case the cancel request number is bigger than | |
| // the actual number of tries. | |
| Assert.True(cancelRequestNum > service.HttpClient.MessageHandler.NumTries + 1); | |
| } | |
| var expectedCalls = Math.Min(service.HttpClient.MessageHandler.NumRedirects + 1, cancelRequestNum); | |
| Assert.Equal(expectedCalls, handler.Calls); | |
| } | |
| } | |
| /// <summary>Tests the execute method in case the service was disposed.</summary> | |
| [] | |
| public void Execute_DisposeService() | |
| { | |
| var handler = new MockMessageHandler(); | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| TestClientServiceRequest request; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| request = new TestClientServiceRequest(service, "POST", new MockRequest()); | |
| } | |
| // the service was disposed before the request was made (and the message handler as well). As a result an | |
| // exception should be thrown before we try to send the request | |
| Assert.Throws<ObjectDisposedException>(() => request.Execute()); | |
| } | |
| /// <summary>A subtest for testing GZip and sync-async calls.</summary> | |
| /// <param name="gzip">Defines if GZip is enabled</param> | |
| /// <param name="async">Defines which method is going to be called (Execute or ExecuteAsync)</param> | |
| private void SubtestExecute_GZip(bool gzip, bool async) | |
| { | |
| var handler = new TestBodyMessageHandler() | |
| { | |
| GZipEnabled = gzip, | |
| ResponseObject = new MockResponse { Id = 100, Name = "sample name" }, | |
| ExpectedRequestObject = new MockRequest { Name = "long long name" } | |
| }; | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| GZipEnabled = gzip, | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| handler.Serializer = service.Serializer; | |
| var request = new TestClientServiceRequest(service, "POST", handler.ExpectedRequestObject); | |
| MockResponse response = null; | |
| if (async) | |
| { | |
| var task = request.ExecuteAsync(); | |
| response = task.Result; | |
| } | |
| else | |
| { | |
| response = request.Execute(); | |
| } | |
| // Note: Even if GZipEnabled is true, we don't need to extract the real string from the GZip stream, | |
| // because in a real request we use HttpClientHandler which its AutomaticDecompression is set to | |
| // System.Net.DecompressionMethods.GZip. | |
| Assert.Equal(1, handler.Calls); | |
| // The returned response should contain ETag, check that the service adds the right ETag property on | |
| // the response. | |
| handler.ResponseObject.ETag = handler.ResponseETag; | |
| Assert.Equal(handler.ResponseObject, response); | |
| } | |
| } | |
| /// <summary>Tests execute when GZip is enabled.</summary> | |
| [] | |
| public void Execute_GZipEnabled() | |
| { | |
| SubtestExecute_GZip(true, false); | |
| } | |
| /// <summary>Tests execute when GZip is disabled.</summary> | |
| [] | |
| public void Execute_GZipDisabled() | |
| { | |
| SubtestExecute_GZip(false, false); | |
| } | |
| /// <summary>Tests async execute when GZip is enabled.</summary> | |
| [] | |
| public void ExecuteAsync_GZipEnabled() | |
| { | |
| SubtestExecute_GZip(true, true); | |
| } | |
| /// <summary>Tests async execute when GZip is disabled.</summary> | |
| [] | |
| public void ExecuteAsync_GZipDisabled() | |
| { | |
| SubtestExecute_GZip(false, true); | |
| } | |
| /// <summary>Tests execute with unicode characters.</summary> | |
| [] | |
| public void Execute_UnicodeCharacters() | |
| { | |
| var handler = new TestBodyMessageHandler() | |
| { | |
| GZipEnabled = false, | |
| ResponseObject = new MockResponse { Id = 100, Name = @"مرحبا العالم" }, | |
| ExpectedRequestObject = new MockRequest { Name = @"مرحبا العالم! 您好,世界!" } | |
| }; | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| GZipEnabled = false, | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| handler.Serializer = service.Serializer; | |
| var request = new TestClientServiceRequest(service, "GET", handler.ExpectedRequestObject); | |
| var response = request.Execute(); | |
| Assert.Equal(1, handler.Calls); | |
| // The returned response should contain ETag, check that the service add the right ETag property on | |
| // the response. | |
| handler.ResponseObject.ETag = handler.ResponseETag; | |
| Assert.Equal(handler.ResponseObject, response); | |
| } | |
| } | |
| /// <summary> | |
| /// A subtest for testing Execute when an exception is thrown while sending the request. This is tested with | |
| /// and without back-off. If back-off handler is attached to the service's message handler, there should be 3 | |
| /// tries (the default value of <seealso cref="ConfigurableMessageHandler.NumTries"/>) before the operation | |
| /// fails. | |
| /// </summary> | |
| /// <param name="backOff">Indicates if back-off handler is attached to the service.</param> | |
| private void SubtestExecute_ThrowException(bool backOff) | |
| { | |
| var handler = new MockMessageHandler(true); | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| // Set the default exponential back-off policy by the input. | |
| initializer.DefaultExponentialBackOffPolicy = backOff ? | |
| ExponentialBackOffPolicy.Exception : ExponentialBackOffPolicy.None; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| Assert.Throws<InvalidOperationMockException>(() => request.Execute()); | |
| int calls = backOff ? service.HttpClient.MessageHandler.NumTries : 1; | |
| Assert.Equal(calls, handler.Calls); | |
| } | |
| } | |
| /// <summary> | |
| /// Tests execute when an exception is thrown during a request and exponential back-off is enabled. | |
| /// </summary> | |
| [] | |
| public void Execute_ThrowException_WithBackOff() | |
| { | |
| SubtestExecute_ThrowException(true); | |
| } | |
| /// <summary> | |
| /// Tests execute when an exception is thrown during a request and exponential back-off is disabled. | |
| /// </summary> | |
| [] | |
| public void Execute_ThrowException_WithoutBackOff() | |
| { | |
| SubtestExecute_ThrowException(false); | |
| } | |
| /// <summary> | |
| /// A subtest for testing ExecuteAsync when an exception is thrown while sending the request. This is tested | |
| /// with and without back-off. If back-off handler is attached to the service's message handler, there should | |
| /// be 3 tries (the default value of <seealso cref="ConfigurableMessageHandler.NumTries"/>) before the | |
| /// operation fails. | |
| /// </summary> | |
| /// <param name="backOff">Indicates if back-off handler is attached to the service.</param> | |
| [] | |
| public async Task SubtestExecuteAsync_ThrowException(bool backOff) | |
| { | |
| var handler = new MockMessageHandler(true); | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| // Configure the back-off behavior by the input. | |
| initializer.DefaultExponentialBackOffPolicy = backOff ? | |
| ExponentialBackOffPolicy.Exception : ExponentialBackOffPolicy.None; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| await Assert.ThrowsAnyAsync<InvalidOperationMockException>(() => request.ExecuteAsync()); | |
| int calls = backOff ? service.HttpClient.MessageHandler.NumTries : 1; | |
| Assert.Equal(calls, handler.Calls); | |
| } | |
| } | |
| /// <summary>Tests execute when server returned an error.</summary> | |
| [] | |
| public void Execute_Error() | |
| { | |
| var handler = new ErrorMessageHandler(); | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| var exception = Assert.Throws<GoogleApiException>(() => request.Execute()); | |
| Assert.Equal(1, handler.Calls); | |
| Assert.Equal(ErrorMessageHandler.ExpectedExceptionMessage, exception.Message); | |
| Assert.Contains(ErrorMessageHandler.ExpectedError, exception.ToString()); | |
| } | |
| } | |
| /// <summary>Tests execute when server returned an error.</summary> | |
| [] | |
| public async Task ExecuteAsync_Error() | |
| { | |
| var handler = new ErrorMessageHandler(); | |
| var initializer = new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| var exception = await Assert.ThrowsAsync<GoogleApiException>(() => request.ExecuteAsync()); | |
| AutoResetEvent resetEvent = new AutoResetEvent(false); | |
| Assert.Equal(1, handler.Calls); | |
| Assert.Equal(ErrorMessageHandler.ExpectedExceptionMessage, exception.Message); | |
| Assert.Contains(ErrorMessageHandler.ExpectedError, exception.ToString()); | |
| } | |
| } | |
| /// <summary>Tests async execution of multiple request simultaneously.</summary> | |
| [] | |
| public async Task ExecuteAsync_Simultaneously() | |
| { | |
| var tasks = new List<Task<MockResponse>>(); | |
| var handler = new ConcurrentCallsHandler(); | |
| var initializer = new BaseClientService.Initializer() | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler), | |
| HttpClientInitializer = new ConcurrentCallsHandler.Initializer() | |
| }; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| int calls = 100; | |
| handler.Serializer = service.Serializer; | |
| CountdownEvent ce = new CountdownEvent(calls); | |
| foreach (var i in Enumerable.Range(1, calls)) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null) { CallNum = i }; | |
| var task = request.ExecuteAsync(); | |
| _ = task.ContinueWith(c => ce.Signal()); | |
| tasks.Add(task); | |
| } | |
| ce.Wait(); | |
| for (var i = 1; i <= tasks.Count; ++i) | |
| { | |
| var response = await tasks[i - 1]; | |
| // check that we got the right response. Name should be equal to the index number modulo 10 (or | |
| // index number plus one module 10, if it's an even request - because even request should fail in | |
| // the first request, but should succeed in the next try). That's the logic of our | |
| // ConcurrentCallHandler, see it core SendAsyncCore method to understand better the logic | |
| var nameIndex = ((i % 2 == 0) ? i + 1 : i) % 10; | |
| Assert.Equal("Name-" + nameIndex, response.Name); | |
| } | |
| // half of the request should succeed in the second call, so total calls should be calls + calls/2 | |
| Assert.Equal(calls + calls / 2, handler.Calls); | |
| } | |
| } | |
| /// <summary>A subtest for testing execute as stream (async and sync).</summary> | |
| private void SubtestExecuteAsStream(bool async) | |
| { | |
| var handler = new TestBodyMessageHandler | |
| { | |
| GZipEnabled = false, | |
| ResponseObject = new MockResponse { Id = 100, Name = "sample name" }, | |
| ExpectedRequestObject = new MockRequest { Name = "long long name" } | |
| }; | |
| var initializer = new BaseClientService.Initializer | |
| { | |
| GZipEnabled = false, | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| using (var service = new MockClientService(initializer)) | |
| { | |
| handler.Serializer = service.Serializer; | |
| var request = new TestClientServiceRequest(service, "GET", handler.ExpectedRequestObject); | |
| MockResponse response = null; | |
| Stream stream = null; | |
| if (async) | |
| { | |
| request.ExecuteAsStreamAsync().ContinueWith(t => | |
| { | |
| stream = t.Result; | |
| }, TaskContinuationOptions.OnlyOnRanToCompletion).Wait(); | |
| } | |
| else | |
| { | |
| stream = request.ExecuteAsStream(); | |
| } | |
| // Read the object. | |
| var str = ExtractStringFromStream(stream); | |
| response = service.Serializer.Deserialize<MockResponse>(str); | |
| Assert.Equal(1, handler.Calls); | |
| Assert.Equal(handler.ResponseObject, response); | |
| } | |
| } | |
| /// <summary>Tests execute stream.</summary> | |
| [] | |
| public void ExecuteAsStream() | |
| { | |
| SubtestExecuteAsStream(false); | |
| } | |
| /// <summary>Tests execute stream (async).</summary> | |
| [] | |
| public void ExecuteAsStreamAsync() | |
| { | |
| SubtestExecuteAsStream(true); | |
| } | |
| /// <summary>Client request which contains query parameters.</summary> | |
| class ClientServiceRequestWithQueryParameters : TestClientServiceRequest | |
| { | |
| [] | |
| public string Required { get; set; } | |
| [] | |
| public string OptionalWithValue { get; set; } | |
| [] | |
| public string OptionalWithValue2 { get; set; } | |
| [] | |
| public string OptionalNull { get; set; } | |
| [] | |
| public string OptionalEmpty { get; set; } | |
| [] | |
| public string OptionalNotPressent { get; set; } | |
| public ClientServiceRequestWithQueryParameters(IClientService service, string method, object body) | |
| : base(service, method, body) | |
| { | |
| RequestParameters.Add("required", new Parameter | |
| { | |
| Name = "required", | |
| IsRequired = true, | |
| ParameterType = "query" | |
| }); | |
| RequestParameters.Add("optionalWithValue", new Parameter | |
| { | |
| Name = "optionalWithValue", | |
| IsRequired = false, | |
| ParameterType = "query", | |
| DefaultValue = "DoesNotDisplay" | |
| }); | |
| RequestParameters.Add("optionalWithValue2", new Parameter | |
| { | |
| Name = "optionalWithValue", | |
| IsRequired = false, | |
| ParameterType = "query", | |
| DefaultValue = "DoesNotDisplay" | |
| }); | |
| RequestParameters.Add("optionalWithNull", new Parameter | |
| { | |
| Name = "optionalWithNull", | |
| IsRequired = false, | |
| ParameterType = "query", | |
| DefaultValue = "c" | |
| }); | |
| RequestParameters.Add("optionalEmpty", new Parameter | |
| { | |
| Name = "optionalEmpty", | |
| IsRequired = false, | |
| ParameterType = "query", | |
| DefaultValue = "d" | |
| }); | |
| RequestParameters.Add("optionalNotPressent", new Parameter | |
| { | |
| Name = "optionalNotPressent", | |
| IsRequired = false, | |
| ParameterType = "query", | |
| DefaultValue = "DoesNotDisplay" | |
| }); | |
| } | |
| } | |
| /// <summary>Tests build request with query parameters.</summary> | |
| [] | |
| public void CreateRequest_QueryParameters() | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| var request = new ClientServiceRequestWithQueryParameters(service, "GET", null); | |
| request.Required = "a"; | |
| request.OptionalWithValue = "b"; | |
| request.OptionalWithValue2 = "DoesNotDisplay"; | |
| request.OptionalNull = null; | |
| request.OptionalEmpty = string.Empty; | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Equal(new Uri("https://build_request_params/restPath0?required=a&optionalWithValue=b&optionalEmpty"), httpRequest.RequestUri); | |
| } | |
| } | |
| /// <summary>Tests build request with missing required query parameter.</summary> | |
| [] | |
| public void CreateRequest_QueryParameterIsMissing() | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| var request = new ClientServiceRequestWithQueryParameters(service, "GET", null); | |
| var exception = Assert.Throws<GoogleApiException>(() => request.CreateRequest()); | |
| Assert.Contains("Parameter \"required\" is missing", exception.Message); | |
| } | |
| } | |
| /// <summary>Client request which contains date query parameter.</summary> | |
| class ClientServiceRequestWithDateQueryParameter : TestClientServiceRequest | |
| { | |
| [] | |
| public DateTime? Min { get; set; } | |
| public ClientServiceRequestWithDateQueryParameter(IClientService service, string method, object body) | |
| : base(service, method, body) | |
| { | |
| RequestParameters.Add("min_time", new Parameter | |
| { | |
| Name = "min_time", | |
| ParameterType = "query" | |
| }); | |
| } | |
| } | |
| /// <summary>Tests build request with date query parameter.</summary> | |
| [] | |
| public void CreateRequest_QueryDateParameter() | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| // Request without the date parameter. | |
| var request = new ClientServiceRequestWithDateQueryParameter(service, "GET", null); | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Equal(new Uri("https://build_request_params/restPath0"), httpRequest.RequestUri); | |
| // Request with the date parameter. | |
| DateTime date = new DateTime(2002, 2, 25, 12, 57, 32, 777, DateTimeKind.Utc); | |
| request.Min = date; | |
| httpRequest = request.CreateRequest(); | |
| // On Mono, the ":" character gets UrlEncoded. | |
| Assert.Contains(httpRequest.RequestUri.AbsoluteUri, new [] { | |
| "https://build_request_params/restPath0?min_time=2002-02-25T12:57:32.777Z", | |
| "https://build_request_params/restPath0?min_time=2002-02-25T12%3A57%3A32.777Z", | |
| }); | |
| } | |
| } | |
| class ClientServiceRequestWithBoolParameter : TestClientServiceRequest | |
| { | |
| [] | |
| public bool? Bool { get; set; } | |
| public ClientServiceRequestWithBoolParameter(IClientService service, string method, object body) | |
| : base(service, method, body) | |
| { | |
| RequestParameters.Add("bool", new Parameter | |
| { | |
| Name = "bool", | |
| ParameterType = "query" | |
| }); | |
| } | |
| } | |
| [] | |
| public void CreateRequest_QueryBoolParameter() | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| // Request without the bool parameter. | |
| var request = new ClientServiceRequestWithBoolParameter(service, "GET", null); | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Equal(new Uri("https://build_request_params/restPath0"), httpRequest.RequestUri); | |
| // Request with the bool parameter true | |
| request.Bool = true; | |
| Assert.Equal(new Uri("https://build_request_params/restPath0?bool=true"), request.CreateRequest().RequestUri); | |
| // Request with the bool parameter false | |
| request.Bool = false; | |
| Assert.Equal(new Uri("https://build_request_params/restPath0?bool=false"), request.CreateRequest().RequestUri); | |
| } | |
| } | |
| /// <summary>Client request which contains path parameters.</summary> | |
| class ClientServiceRequestWithPathParameters : TestClientServiceRequest | |
| { | |
| [] | |
| public int TestParameterA { get; set; } | |
| [] | |
| public int TestParameterB { get; set; } | |
| public int TestParameterC { get; set; } | |
| public ClientServiceRequestWithPathParameters(IClientService service, string method, object body) | |
| : base(service, method, body) | |
| { | |
| RequestParameters.Add("path1", new Parameter | |
| { | |
| Name = "path1", | |
| ParameterType = "path" | |
| }); | |
| RequestParameters.Add("path2", new Parameter | |
| { | |
| Name = "path2", | |
| ParameterType = "path", | |
| }); | |
| } | |
| public override string RestPath | |
| { | |
| get { return "restPath/{path1}/something/{path2}"; } | |
| } | |
| } | |
| /// <summary>Tests build request with path parameters.</summary> | |
| [] | |
| public void CreateRequest_PathParameters() | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| var request = new ClientServiceRequestWithPathParameters(service, "GET", null); | |
| request.TestParameterA = 42; | |
| request.TestParameterB = 43; | |
| request.TestParameterC = 44; // ignore it, because it doesn't contain RequestParameter attribute | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Equal(new Uri("https://build_request_params/restPath/42/something/43"), httpRequest.RequestUri); | |
| } | |
| } | |
| private const string SimpleDeveloperKey = "ABC123"; | |
| private const string ComplexDeveloperKey = "?&^% ABC123"; | |
| /// <summary>Tests build request with simple developer key.</summary> | |
| [] | |
| public void CreateRequest_DeveloperKey() | |
| { | |
| var initializer = new BaseClientService.Initializer | |
| { | |
| ApiKey = SimpleDeveloperKey | |
| }; | |
| using (var service = new MockClientService(initializer, "https://build_request_params")) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Equal(new Uri("https://build_request_params/restPath0?key=" + SimpleDeveloperKey), httpRequest.RequestUri); | |
| } | |
| } | |
| /// <summary>Tests build request with complex developer key.</summary> | |
| [] | |
| public void CreateRequest_DeveloperKey_RequiresEscape() | |
| { | |
| var initializer = new BaseClientService.Initializer | |
| { | |
| ApiKey = ComplexDeveloperKey | |
| }; | |
| using (var service = new MockClientService(initializer, "https://build_request_params")) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Equal(new Uri("https://build_request_params/restPath0?key=%3F%26%5E%25%20%20ABC123"), httpRequest.RequestUri); | |
| } | |
| } | |
| /// <summary>Tests if invalid method throws an exception.</summary> | |
| [] | |
| public void CreateRequest_UnsupportedMethods() | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| var request = new TestClientServiceRequest(service, "Unsupported", null); | |
| Assert.Throws<ArgumentOutOfRangeException>(() => request.CreateRequest()); | |
| } | |
| } | |
| /// <summary>Tests that valid method doesn't throw an exception.</summary> | |
| [] | |
| public void CreateRequest_SupportedMethods() | |
| { | |
| SubtestCreateRequest_SupportedMethod(HttpConsts.Get); | |
| SubtestCreateRequest_SupportedMethod(HttpConsts.Put); | |
| SubtestCreateRequest_SupportedMethod(HttpConsts.Post); | |
| SubtestCreateRequest_SupportedMethod(HttpConsts.Patch); | |
| SubtestCreateRequest_SupportedMethod(HttpConsts.Delete); | |
| } | |
| private void SubtestCreateRequest_SupportedMethod(string method) | |
| { | |
| using (var service = new MockClientService("https://build_request_params")) | |
| { | |
| var request = new TestClientServiceRequest(service, method, null); | |
| var httpRequest = request.CreateRequest(); | |
| } | |
| } | |
| /// <summary>Tests the create request method with different ETags.</summary> | |
| [] | |
| public void CreateRequest_ETag() | |
| { | |
| var body = new MockRequest { Name = "long long name" }; | |
| using (var service = new MockClientService()) | |
| { | |
| // No ETag (ETag = null). | |
| var request = new TestClientServiceRequest(service, HttpConsts.Get, body); | |
| var httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfMatch); | |
| Assert.Empty(httpRequest.Headers.IfNoneMatch); | |
| // ETag has a value, but ETag action is ignored. | |
| body.ETag = "\"ETAG_HERE\""; | |
| request = new TestClientServiceRequest(service, HttpConsts.Get, body); | |
| request.ETagAction = ETagAction.Ignore; | |
| httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfMatch); | |
| Assert.Empty(httpRequest.Headers.IfNoneMatch); | |
| // ETag has a value, so use default action (Get -> If-None-Match). | |
| request = new TestClientServiceRequest(service, HttpConsts.Get, body); | |
| httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfMatch); | |
| Assert.Single(httpRequest.Headers.IfNoneMatch); | |
| Assert.Equal(new EntityTagHeaderValue(body.ETag), httpRequest.Headers.IfNoneMatch.First()); | |
| // ETag has a value, so use default action (Post -> If-Match). | |
| request = new TestClientServiceRequest(service, HttpConsts.Post, body); | |
| httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfNoneMatch); | |
| Assert.Single(httpRequest.Headers.IfMatch); | |
| Assert.Equal(new EntityTagHeaderValue(body.ETag), httpRequest.Headers.IfMatch.First()); | |
| // ETag has a value, default is override, use the specified ETag action. | |
| request = new TestClientServiceRequest(service, HttpConsts.Post, body); | |
| request.ETagAction = ETagAction.IfNoneMatch; | |
| httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfMatch); | |
| Assert.Single(httpRequest.Headers.IfNoneMatch); | |
| Assert.Equal(new EntityTagHeaderValue(body.ETag), httpRequest.Headers.IfNoneMatch.First()); | |
| // ETag has a value, default is override, use the specified ETag action. | |
| request = new TestClientServiceRequest(service, HttpConsts.Get, body); | |
| request.ETagAction = ETagAction.IfMatch; | |
| httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfNoneMatch); | |
| Assert.Single(httpRequest.Headers.IfMatch); | |
| Assert.Equal(new EntityTagHeaderValue(body.ETag), httpRequest.Headers.IfMatch.First()); | |
| // Fixing bug https://code.google.com/p/google-api-dotnet-client/issues/detail?id=464. | |
| // If etag doesn't contain quotas - catch the format exception and log. | |
| body.ETag = "ETAG"; | |
| request = new TestClientServiceRequest(service, HttpConsts.Post, body); | |
| httpRequest = request.CreateRequest(); | |
| Assert.Empty(httpRequest.Headers.IfNoneMatch); | |
| Assert.Empty(httpRequest.Headers.IfMatch); | |
| } | |
| } | |
| /// <summary>Tests that get default ETag action works as expected.</summary> | |
| [] | |
| public void GetDefaultETagActionTest() | |
| { | |
| Assert.Equal(ETagAction.IfNoneMatch, ClientServiceRequest<object>.GetDefaultETagAction(HttpConsts.Get)); | |
| Assert.Equal(ETagAction.IfMatch, ClientServiceRequest<object>.GetDefaultETagAction(HttpConsts.Post)); | |
| Assert.Equal(ETagAction.IfMatch, ClientServiceRequest<object>.GetDefaultETagAction(HttpConsts.Patch)); | |
| Assert.Equal(ETagAction.IfMatch, ClientServiceRequest<object>.GetDefaultETagAction(HttpConsts.Put)); | |
| Assert.Equal(ETagAction.IfMatch, ClientServiceRequest<object>.GetDefaultETagAction(HttpConsts.Delete)); | |
| Assert.Equal(ETagAction.Ignore, ClientServiceRequest<object>.GetDefaultETagAction("INVALID")); | |
| } | |
| private class TestHttpHandler : HttpMessageHandler | |
| { | |
| public TestHttpHandler(Func<HttpRequestMessage, HttpResponseMessage> fn) => _fn = fn; | |
| private Func<HttpRequestMessage, HttpResponseMessage> _fn; | |
| protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => | |
| Task.FromResult(_fn(request)); | |
| } | |
| // This is a valid JSON, but not one we recognize | |
| const string ErrorJson_Unknown = | |
| "{ " + | |
| "\"error\":\"invalid_target\", " + | |
| "\"error_description\":\"The target service indicated by the \\\"audience\\\" parameters is invalid. This might either be because the pool or provider is disabled or deleted or because it doesn't exist.\" " + | |
| "}"; | |
| // HttpContent and HttpStatusCode for the response. | |
| // Expected ex.Message and ex.ToString() content. | |
| public static TheoryData<HttpContent, HttpStatusCode, string, string> DeserializeErrorThrows => | |
| new TheoryData<HttpContent, HttpStatusCode, string, string> | |
| { | |
| { | |
| new StringContent(ErrorJson_Unknown), | |
| HttpStatusCode.BadRequest, | |
| "The service TestService has thrown an exception. HttpStatusCode is BadRequest. No error message was specified.", | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"HttpStatusCode is BadRequest.{Environment.NewLine}" + | |
| $"No JSON error details were specified.{Environment.NewLine}" + | |
| $"Raw error details are: {ErrorJson_Unknown}" | |
| }, | |
| { | |
| new StringContent(""), | |
| HttpStatusCode.BadRequest, | |
| "The service TestService has thrown an exception. HttpStatusCode is BadRequest. No error message was specified.", | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"HttpStatusCode is BadRequest.{Environment.NewLine}" + | |
| $"No JSON error details were specified.{Environment.NewLine}" + | |
| $"Raw error details are empty or white spaces only." | |
| }, | |
| { | |
| new StringContent("not json"), | |
| HttpStatusCode.BadRequest, | |
| $"The service TestService has thrown an exception. HttpStatusCode is BadRequest. No error message was specified.", | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"HttpStatusCode is BadRequest.{Environment.NewLine}" + | |
| $"No JSON error details were specified.{Environment.NewLine}" + | |
| $"Raw error details are: not json" | |
| }, | |
| { | |
| null, | |
| 0, | |
| $"The service TestService has thrown an exception. No HttpStatusCode was specified. No error message was specified.", | |
| // Even though we tell our handler to return a response with null content, | |
| // in .NET 6 we get a StringContent with empty string content. | |
| // This just documents that behaviour. | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"No HttpStatusCode was specified.{Environment.NewLine}" + | |
| $"No JSON error details were specified.{Environment.NewLine}" + | |
| $"Raw error details are empty or white spaces only." | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"No HttpStatusCode was specified.{Environment.NewLine}" + | |
| $"No error details were specified." | |
| }, | |
| { | |
| null, | |
| HttpStatusCode.BadRequest, | |
| $"The service TestService has thrown an exception. HttpStatusCode is BadRequest. No error message was specified.", | |
| // Even though we tell our handler to return a response with null content, | |
| // in .NET 6 we get a StringContent with empty string content. | |
| // This just documents that behaviour. | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"HttpStatusCode is BadRequest.{Environment.NewLine}" + | |
| $"No JSON error details were specified.{Environment.NewLine}" + | |
| $"Raw error details are empty or white spaces only." | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"HttpStatusCode is BadRequest.{Environment.NewLine}" + | |
| $"No error details were specified." | |
| } | |
| }; | |
| // Content and HttpStatusCode for the response. | |
| // Expected error.Message and error.ToString(). | |
| public static TheoryData<string, HttpStatusCode, string, string> DeserializeErrorSucceeds => | |
| new TheoryData<string, HttpStatusCode, string, string> | |
| { | |
| { | |
| "{\"error\":{\"message\":\"json error\"}}", | |
| HttpStatusCode.BadRequest, | |
| $"json error", | |
| $"Google.Apis.Requests.RequestError{Environment.NewLine}" + | |
| $"json error [0]{Environment.NewLine}" + | |
| $"No individual errors{Environment.NewLine}" | |
| }, | |
| { | |
| "{\"error\":{\"message\":\"json error\",\"errors\":[{\"message\":\"individual error\",\"domain\":\"global\",\"reason\":\"badRequest\"}]}}", | |
| HttpStatusCode.BadRequest, | |
| $"json error", | |
| $"Google.Apis.Requests.RequestError{Environment.NewLine}" + | |
| $"json error [0]{Environment.NewLine}" + | |
| $"Errors [{Environment.NewLine}" + | |
| $"\tMessage[individual error] Location[ - ] Reason[badRequest] Domain[global]{Environment.NewLine}" + | |
| $"]{Environment.NewLine}" | |
| }, | |
| }; | |
| // HttpContent and HttpStatusCode for the response. | |
| // Expected ex.Message and ex.ToString() content. | |
| public static TheoryData<HttpContent, HttpStatusCode, string, string> ResponseErrorData | |
| { | |
| get | |
| { | |
| var data = new TheoryData<HttpContent, HttpStatusCode, string, string>(); | |
| // Regardless of whether deserialization of the error succeeds or fails, | |
| // the request execution has failed, so service client should throw. | |
| foreach(var success in DeserializeErrorSucceeds) | |
| { | |
| string content = (string)success[0]; | |
| HttpStatusCode statusCode = (HttpStatusCode)success[1]; | |
| string errorMessage = (string)success[2]; | |
| string errorToString = (string)success[3]; | |
| data.Add( | |
| new StringContent(content), | |
| statusCode, | |
| $"The service TestService has thrown an exception. HttpStatusCode is {statusCode}. {errorMessage}", | |
| $"The service TestService has thrown an exception.{Environment.NewLine}" + | |
| $"HttpStatusCode is {statusCode}.{Environment.NewLine}" + | |
| $"{errorToString}"); | |
| } | |
| foreach(var failure in DeserializeErrorThrows) | |
| { | |
| data.Add((HttpContent)failure[0], (HttpStatusCode)failure[1], (string)failure[2], (string)failure[3]); | |
| } | |
| return data; | |
| } | |
| } | |
| [] | |
| [] | |
| public async Task DeserializeError_Throws( | |
| HttpContent content, HttpStatusCode statusCode, string expectedExceptionMessage, string expectedToStringContent) | |
| { | |
| var response = new HttpResponseMessage(statusCode) | |
| { | |
| Content = content | |
| }; | |
| using (var service = new MockClientService()) | |
| { | |
| var ex = await Assert.ThrowsAsync<GoogleApiException>(() => service.DeserializeError(response)); | |
| Assert.Equal(expectedExceptionMessage, ex.Message); | |
| Assert.Equal(statusCode, ex.HttpStatusCode); | |
| Assert.Contains(expectedToStringContent, ex.ToString()); | |
| } | |
| } | |
| [] | |
| [] | |
| public async Task DeserializeError_Succeeds(string content, HttpStatusCode statusCode, string expectedErrorMessage, string expectedToString) | |
| { | |
| var response = new HttpResponseMessage(statusCode) | |
| { | |
| Content = new StringContent(content) | |
| }; | |
| using (var service = new MockClientService()) | |
| { | |
| RequestError error = await service.DeserializeError(response); | |
| Assert.False(error.IsOnlyRawContent); | |
| Assert.Equal(expectedErrorMessage, error.Message); | |
| Assert.Equal(expectedToString, error.ToString()); | |
| Assert.Equal(content, error.ErrorResponseContent); | |
| } | |
| } | |
| [] | |
| [] | |
| public void ResponseError(HttpContent content, HttpStatusCode statusCode, string expectedExceptionMessage, string expectedToStringContent) | |
| { | |
| var response = new HttpResponseMessage(statusCode) | |
| { | |
| Content = content | |
| }; | |
| using (var service = new MockClientService(new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(new TestHttpHandler(_ => response)) | |
| })) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| var ex = Assert.Throws<GoogleApiException>(() => request.Execute()); | |
| Assert.Equal(expectedExceptionMessage, ex.Message); | |
| Assert.Contains(expectedToStringContent, ex.ToString()); | |
| Assert.Equal(statusCode, ex.HttpStatusCode); | |
| } | |
| } | |
| private class TestUnsuccessfulResponseHandler : IHttpUnsuccessfulResponseHandler | |
| { | |
| public int Count { get; private set; } = 0; | |
| public Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args) => Task.FromResult(++Count == 0); | |
| } | |
| [] | |
| public void PerCallUnsuccessfulResponseHandler() | |
| { | |
| var response = new HttpResponseMessage(HttpStatusCode.BadRequest); | |
| using (var service = new MockClientService(new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(new TestHttpHandler(_ => response)) | |
| })) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| Assert.Throws<ArgumentNullException>(() => request.AddUnsuccessfulResponseHandler(null)); | |
| var handler = new TestUnsuccessfulResponseHandler(); | |
| request.AddUnsuccessfulResponseHandler(handler); | |
| var httpRequest = request.CreateRequest(); | |
| var properties = TestHelpers.GetRequestProperties(httpRequest); | |
| Assert.Equal(handler, ((List<IHttpUnsuccessfulResponseHandler>) properties[ConfigurableMessageHandler.UnsuccessfulResponseHandlerKey]).Single()); | |
| Assert.Throws<GoogleApiException>(() => request.Execute()); | |
| Assert.True(handler.Count > 0); | |
| } | |
| } | |
| private class TestExceptionHandler : IHttpExceptionHandler | |
| { | |
| public int Count { get; private set; } = 0; | |
| public Task<bool> HandleExceptionAsync(HandleExceptionArgs args) => Task.FromResult(++Count == 0); | |
| } | |
| [] | |
| public void PerCallExceptionHandler() | |
| { | |
| using (var service = new MockClientService(new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(new TestHttpHandler(_ => throw new Exception("mymsg"))) | |
| })) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| Assert.Throws<ArgumentNullException>(() => request.AddExceptionHandler(null)); | |
| var handler = new TestExceptionHandler(); | |
| request.AddExceptionHandler(handler); | |
| var httpRequest = request.CreateRequest(); | |
| var properties = TestHelpers.GetRequestProperties(httpRequest); | |
| Assert.Equal(handler, ((List<IHttpExceptionHandler>) properties[ConfigurableMessageHandler.ExceptionHandlerKey]).Single()); | |
| var ex = Assert.Throws<Exception>(() => request.Execute()); | |
| Assert.Equal("mymsg", ex.Message); | |
| Assert.True(handler.Count > 0); | |
| } | |
| } | |
| private class TestExecuteInterceptor : IHttpExecuteInterceptor | |
| { | |
| public int Count { get; private set; } = 0; | |
| public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken) => Task.FromResult(++Count == 0); | |
| } | |
| [] | |
| public void PerCallExecutionInterceptor() | |
| { | |
| using (var service = new MockClientService(new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(new TestHttpHandler(_ => throw new Exception("mymsg"))) | |
| })) | |
| { | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| Assert.Throws<ArgumentNullException>(() => request.AddExecuteInterceptor(null)); | |
| var interceptor = new TestExecuteInterceptor(); | |
| request.AddExecuteInterceptor(interceptor); | |
| var httpRequest = request.CreateRequest(); | |
| var properties = TestHelpers.GetRequestProperties(httpRequest); | |
| Assert.Equal(interceptor, ((List<IHttpExecuteInterceptor>) properties[ConfigurableMessageHandler.ExecuteInterceptorKey]).Single()); | |
| Assert.Throws<Exception>(() => request.Execute()); | |
| Assert.True(interceptor.Count > 0); | |
| } | |
| } | |
| [] | |
| [] | |
| [] | |
| [] | |
| public void GenerateRequestUri(string idValue, string expectedRequestUri) | |
| { | |
| var request = new TestClientServiceRequest(new MockClientService("http://www.example.com"), "POST", null) | |
| { | |
| OverrideRestPath = "test/{id}", | |
| Id = idValue | |
| }; | |
| string requestUri = request.GenerateRequestUriPublic(); | |
| Assert.Equal(expectedRequestUri, requestUri); | |
| } | |
| /// <summary> | |
| /// Client request which contains validated parameters. Both "optional" and "required" | |
| /// have patterns of "valid" (so only a value of "valid" should pass validation). | |
| /// </summary> | |
| class ClientServiceRequestWithValidatedParameters : TestClientServiceRequest | |
| { | |
| [] | |
| public string Required { get; set; } | |
| [] | |
| public string Optional { get; set; } | |
| public ClientServiceRequestWithValidatedParameters(IClientService service) | |
| : base(service, "GET", body: null) | |
| { | |
| RequestParameters.Add("required", new Parameter | |
| { | |
| Name = "required", | |
| IsRequired = true, | |
| ParameterType = "query", | |
| Pattern = "^valid$" | |
| }); | |
| RequestParameters.Add("optional", new Parameter | |
| { | |
| Name = "optional", | |
| IsRequired = false, | |
| ParameterType = "query", | |
| Pattern = "^valid$" | |
| }); | |
| } | |
| } | |
| [] | |
| public void Validation_ValidValues(bool serviceValidation, bool requestValidation, | |
| [CombinatorialValues("valid")] string requiredValue, [CombinatorialValues("valid", null)] string optionalValue) | |
| { | |
| var request = CreateClientServiceRequestWithValidatedParameters(serviceValidation, requestValidation, requiredValue, optionalValue); | |
| // We're just checking that this doesn't throw. | |
| request.CreateRequest(); | |
| } | |
| [] | |
| [] | |
| [] | |
| // Even if we don't validate patterns, a required parameter is still required. | |
| [] | |
| [] | |
| // Request validation overrides service validation | |
| [] | |
| public void Validation_InvalidValues_Throwing(bool serviceValidation, bool? requestValidation, string requiredValue, string optionalValue) | |
| { | |
| var request = CreateClientServiceRequestWithValidatedParameters(serviceValidation, requestValidation, requiredValue, optionalValue); | |
| Assert.Throws<GoogleApiException>(() => request.CreateRequest()); | |
| } | |
| [] | |
| [] | |
| [] | |
| // Request validation overrides service validation | |
| [] | |
| public void Validation_InvalidValues_Ignored(bool serviceValidation, bool? requestValidation, string requiredValue, string optionalValue) | |
| { | |
| var request = CreateClientServiceRequestWithValidatedParameters(serviceValidation, requestValidation, requiredValue, optionalValue); | |
| // We're just checking that this doesn't throw. | |
| request.CreateRequest(); | |
| } | |
| private ClientServiceRequestWithValidatedParameters CreateClientServiceRequestWithValidatedParameters( | |
| bool serviceValidation, bool? requestValidation, string requiredValue, string optionalValue) | |
| { | |
| var service = new MockClientService(new BaseClientService.Initializer { ValidateParameters = serviceValidation }); | |
| return new ClientServiceRequestWithValidatedParameters(service) | |
| { | |
| ValidateParameters = requestValidation, | |
| Required = requiredValue, | |
| Optional = optionalValue | |
| }; | |
| } | |
| [] | |
| public async Task ApiVersion_NotSet() | |
| { | |
| HttpRequestMessage lastRequest = null; | |
| var handler = new TestHttpHandler(req => | |
| { | |
| lastRequest = req; | |
| return new HttpResponseMessage | |
| { | |
| // In .NET Framework 4.6.2 Content is null and since the request here succeedds, | |
| // we attempt to parse the response and get NullReferenceException. | |
| // Note this is a test problem and not a production code problem because Content | |
| // won't be null on a successful response. | |
| // In .NET 6, Content will be EmptyContent. | |
| Content = new StringContent("") | |
| }; | |
| }); | |
| var initializer = new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| var service = new MockClientService(initializer); | |
| var request = new TestClientServiceRequest(service, "GET", null); | |
| await request.ExecuteAsync(); | |
| Assert.NotNull(lastRequest); | |
| Assert.False(lastRequest.Headers.Contains(ClientServiceRequest<MockRequest>.ApiVersionHeaderName)); | |
| } | |
| [] | |
| public async Task ApiVersion_Set() | |
| { | |
| HttpRequestMessage lastRequest = null; | |
| var handler = new TestHttpHandler(req => | |
| { | |
| lastRequest = req; | |
| return new HttpResponseMessage | |
| { | |
| // In .NET Framework 4.6.2 Content is null and since the request here succeedds, | |
| // we attempt to parse the response and get NullReferenceException. | |
| // Note this is a test problem and not a production code problem because Content | |
| // won't be null on a successful response. | |
| // In .NET 6, Content will be EmptyContent. | |
| Content = new StringContent("") | |
| }; | |
| }); | |
| var initializer = new BaseClientService.Initializer | |
| { | |
| HttpClientFactory = new MockHttpClientFactory(handler) | |
| }; | |
| var service = new MockClientService(initializer); | |
| var request = new TestClientServiceRequest(service, "GET", null) { ApiVersionImpl = "test-version" }; | |
| await request.ExecuteAsync(); | |
| Assert.NotNull(lastRequest); | |
| var value = Assert.Single(lastRequest.Headers.GetValues(ClientServiceRequest<MockRequest>.ApiVersionHeaderName)); | |
| Assert.Equal("test-version", value); | |
| } | |
| } | |
| } | |