1 // OpenTween - Client of Twitter
2 // Copyright (c) 2016 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
23 using System.Collections.Generic;
27 using System.Net.Http;
28 using System.Net.Http.Headers;
29 using System.Reflection;
30 using System.Runtime.InteropServices;
32 using System.Threading;
33 using System.Threading.Tasks;
37 using OpenTween.Api.DataModel;
40 namespace OpenTween.Connection
42 public class TwitterApiConnectionTest
44 public TwitterApiConnectionTest()
45 => this.MyCommonSetup();
47 private void MyCommonSetup()
49 var mockAssembly = new Mock<_Assembly>();
50 mockAssembly.Setup(m => m.GetName()).Returns(new AssemblyName("OpenTween"));
52 MyCommon.EntryAssembly = mockAssembly.Object;
56 public async Task SendAsync_Test()
58 using var mockHandler = new HttpMessageHandlerMock();
59 using var http = new HttpClient(mockHandler);
60 using var apiConnection = new TwitterApiConnection();
61 apiConnection.Http = http;
63 mockHandler.Enqueue(x =>
65 Assert.Equal(HttpMethod.Get, x.Method);
66 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
67 x.RequestUri.GetLeftPart(UriPartial.Path));
69 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
71 Assert.Equal("1111", query["aaaa"]);
72 Assert.Equal("2222", query["bbbb"]);
74 return new HttpResponseMessage(HttpStatusCode.OK)
76 Content = new StringContent("\"hogehoge\""),
80 var request = new GetRequest
82 RequestUri = new("hoge/tetete.json", UriKind.Relative),
83 Query = new Dictionary<string, string>
88 EndpointName = "/hoge/tetete",
91 using var response = await apiConnection.SendAsync(request);
93 Assert.Equal("hogehoge", await response.ReadAsJson<string>());
95 Assert.Equal(0, mockHandler.QueueCount);
99 public async Task SendAsync_UpdateRateLimitTest()
101 using var mockHandler = new HttpMessageHandlerMock();
102 using var http = new HttpClient(mockHandler);
103 using var apiConnection = new TwitterApiConnection();
104 apiConnection.Http = http;
106 mockHandler.Enqueue(x =>
108 Assert.Equal(HttpMethod.Get, x.Method);
109 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
110 x.RequestUri.GetLeftPart(UriPartial.Path));
112 return new HttpResponseMessage(HttpStatusCode.OK)
116 { "X-Rate-Limit-Limit", "150" },
117 { "X-Rate-Limit-Remaining", "100" },
118 { "X-Rate-Limit-Reset", "1356998400" },
119 { "X-Access-Level", "read-write-directmessages" },
121 Content = new StringContent("\"hogehoge\""),
125 var apiStatus = new TwitterApiStatus();
126 MyCommon.TwitterApiInfo = apiStatus;
128 var request = new GetRequest
130 RequestUri = new("hoge/tetete.json", UriKind.Relative),
131 EndpointName = "/hoge/tetete",
134 using var response = await apiConnection.SendAsync(request);
136 Assert.Equal(TwitterApiAccessLevel.ReadWriteAndDirectMessage, apiStatus.AccessLevel);
137 Assert.Equal(new ApiLimit(150, 100, new DateTimeUtc(2013, 1, 1, 0, 0, 0)), apiStatus.AccessLimit["/hoge/tetete"]);
139 Assert.Equal(0, mockHandler.QueueCount);
143 public async Task SendAsync_ErrorStatusTest()
145 using var mockHandler = new HttpMessageHandlerMock();
146 using var http = new HttpClient(mockHandler);
147 using var apiConnection = new TwitterApiConnection();
148 apiConnection.Http = http;
150 mockHandler.Enqueue(x =>
152 return new HttpResponseMessage(HttpStatusCode.BadGateway)
154 Content = new StringContent("### Invalid JSON Response ###"),
158 var request = new GetRequest
160 RequestUri = new("hoge/tetete.json", UriKind.Relative),
163 var exception = await Assert.ThrowsAsync<TwitterApiException>(
164 () => apiConnection.SendAsync(request)
167 // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する
168 Assert.Equal("BadGateway", exception.Message);
169 Assert.Null(exception.ErrorResponse);
171 Assert.Equal(0, mockHandler.QueueCount);
175 public async Task SendAsync_ErrorJsonTest()
177 using var mockHandler = new HttpMessageHandlerMock();
178 using var http = new HttpClient(mockHandler);
179 using var apiConnection = new TwitterApiConnection();
180 apiConnection.Http = http;
182 mockHandler.Enqueue(x =>
184 return new HttpResponseMessage(HttpStatusCode.Forbidden)
186 Content = new StringContent("""{"errors":[{"code":187,"message":"Status is a duplicate."}]}"""),
190 var request = new GetRequest
192 RequestUri = new("hoge/tetete.json", UriKind.Relative),
195 var exception = await Assert.ThrowsAsync<TwitterApiException>(
196 () => apiConnection.SendAsync(request)
199 // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する
200 Assert.Equal("DuplicateStatus", exception.Message);
202 Assert.Equal(TwitterErrorCode.DuplicateStatus, exception.Errors[0].Code);
203 Assert.Equal("Status is a duplicate.", exception.Errors[0].Message);
205 Assert.Equal(0, mockHandler.QueueCount);
209 public async Task HandleTimeout_SuccessTest()
211 static async Task<int> AsyncFunc(CancellationToken token)
213 await Task.Delay(10);
214 token.ThrowIfCancellationRequested();
218 var timeout = TimeSpan.FromMilliseconds(200);
219 var ret = await TwitterApiConnection.HandleTimeout(AsyncFunc, timeout);
221 Assert.Equal(1, ret);
225 public async Task HandleTimeout_TimeoutTest()
227 var tcs = new TaskCompletionSource<bool>();
229 async Task<int> AsyncFunc(CancellationToken token)
231 await Task.Delay(200);
232 tcs.SetResult(token.IsCancellationRequested);
236 var timeout = TimeSpan.FromMilliseconds(10);
237 await Assert.ThrowsAsync<OperationCanceledException>(
238 () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
241 var cancelRequested = await tcs.Task;
242 Assert.True(cancelRequested);
246 public async Task HandleTimeout_ThrowExceptionAfterTimeoutTest()
248 var tcs = new TaskCompletionSource<int>();
250 async Task<int> AsyncFunc(CancellationToken token)
252 await Task.Delay(100);
254 throw new Exception();
257 var timeout = TimeSpan.FromMilliseconds(10);
258 await Assert.ThrowsAsync<OperationCanceledException>(
259 () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
262 // キャンセル後に AsyncFunc で発生した例外が無視される(UnobservedTaskException イベントを発生させない)ことをチェックする
264 void UnobservedExceptionHandler(object s, UnobservedTaskExceptionEventArgs e)
267 TaskScheduler.UnobservedTaskException += UnobservedExceptionHandler;
270 await Task.Delay(10);
271 GC.Collect(); // UnobservedTaskException は Task のデストラクタで呼ばれるため強制的に GC を実行する
272 await Task.Delay(10);
276 TaskScheduler.UnobservedTaskException -= UnobservedExceptionHandler;