OSDN Git Service

TwitterApiConnection.GetAsyncメソッドを削除
[opentween/open-tween.git] / OpenTween.Tests / Connection / TwitterApiConnectionTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2016 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
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)
10 // any later version.
11 //
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
15 // for more details.
16 //
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.
21
22 using System;
23 using System.Collections.Generic;
24 using System.IO;
25 using System.Linq;
26 using System.Net;
27 using System.Net.Http;
28 using System.Net.Http.Headers;
29 using System.Reflection;
30 using System.Runtime.InteropServices;
31 using System.Text;
32 using System.Threading;
33 using System.Threading.Tasks;
34 using System.Web;
35 using Moq;
36 using OpenTween.Api;
37 using OpenTween.Api.DataModel;
38 using Xunit;
39
40 namespace OpenTween.Connection
41 {
42     public class TwitterApiConnectionTest
43     {
44         public TwitterApiConnectionTest()
45             => this.MyCommonSetup();
46
47         private void MyCommonSetup()
48         {
49             var mockAssembly = new Mock<_Assembly>();
50             mockAssembly.Setup(m => m.GetName()).Returns(new AssemblyName("OpenTween"));
51
52             MyCommon.EntryAssembly = mockAssembly.Object;
53         }
54
55         [Fact]
56         public async Task SendAsync_Test()
57         {
58             using var mockHandler = new HttpMessageHandlerMock();
59             using var http = new HttpClient(mockHandler);
60             using var apiConnection = new TwitterApiConnection();
61             apiConnection.Http = http;
62
63             mockHandler.Enqueue(x =>
64             {
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));
68
69                 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
70
71                 Assert.Equal("1111", query["aaaa"]);
72                 Assert.Equal("2222", query["bbbb"]);
73
74                 return new HttpResponseMessage(HttpStatusCode.OK)
75                 {
76                     Content = new StringContent("\"hogehoge\""),
77                 };
78             });
79
80             var request = new GetRequest
81             {
82                 RequestUri = new("hoge/tetete.json", UriKind.Relative),
83                 Query = new Dictionary<string, string>
84                 {
85                     ["aaaa"] = "1111",
86                     ["bbbb"] = "2222",
87                 },
88                 EndpointName = "/hoge/tetete",
89             };
90
91             using var response = await apiConnection.SendAsync(request);
92
93             Assert.Equal("hogehoge", await response.ReadAsJson<string>());
94
95             Assert.Equal(0, mockHandler.QueueCount);
96         }
97
98         [Fact]
99         public async Task SendAsync_UpdateRateLimitTest()
100         {
101             using var mockHandler = new HttpMessageHandlerMock();
102             using var http = new HttpClient(mockHandler);
103             using var apiConnection = new TwitterApiConnection();
104             apiConnection.Http = http;
105
106             mockHandler.Enqueue(x =>
107             {
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));
111
112                 return new HttpResponseMessage(HttpStatusCode.OK)
113                 {
114                     Headers =
115                     {
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" },
120                     },
121                     Content = new StringContent("\"hogehoge\""),
122                 };
123             });
124
125             var apiStatus = new TwitterApiStatus();
126             MyCommon.TwitterApiInfo = apiStatus;
127
128             var request = new GetRequest
129             {
130                 RequestUri = new("hoge/tetete.json", UriKind.Relative),
131                 EndpointName = "/hoge/tetete",
132             };
133
134             using var response = await apiConnection.SendAsync(request);
135
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"]);
138
139             Assert.Equal(0, mockHandler.QueueCount);
140         }
141
142         [Fact]
143         public async Task SendAsync_ErrorStatusTest()
144         {
145             using var mockHandler = new HttpMessageHandlerMock();
146             using var http = new HttpClient(mockHandler);
147             using var apiConnection = new TwitterApiConnection();
148             apiConnection.Http = http;
149
150             mockHandler.Enqueue(x =>
151             {
152                 return new HttpResponseMessage(HttpStatusCode.BadGateway)
153                 {
154                     Content = new StringContent("### Invalid JSON Response ###"),
155                 };
156             });
157
158             var request = new GetRequest
159             {
160                 RequestUri = new("hoge/tetete.json", UriKind.Relative),
161             };
162
163             var exception = await Assert.ThrowsAsync<TwitterApiException>(
164                 () => apiConnection.SendAsync(request)
165             );
166
167             // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する
168             Assert.Equal("BadGateway", exception.Message);
169             Assert.Null(exception.ErrorResponse);
170
171             Assert.Equal(0, mockHandler.QueueCount);
172         }
173
174         [Fact]
175         public async Task SendAsync_ErrorJsonTest()
176         {
177             using var mockHandler = new HttpMessageHandlerMock();
178             using var http = new HttpClient(mockHandler);
179             using var apiConnection = new TwitterApiConnection();
180             apiConnection.Http = http;
181
182             mockHandler.Enqueue(x =>
183             {
184                 return new HttpResponseMessage(HttpStatusCode.Forbidden)
185                 {
186                     Content = new StringContent("""{"errors":[{"code":187,"message":"Status is a duplicate."}]}"""),
187                 };
188             });
189
190             var request = new GetRequest
191             {
192                 RequestUri = new("hoge/tetete.json", UriKind.Relative),
193             };
194
195             var exception = await Assert.ThrowsAsync<TwitterApiException>(
196                 () => apiConnection.SendAsync(request)
197             );
198
199             // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する
200             Assert.Equal("DuplicateStatus", exception.Message);
201
202             Assert.Equal(TwitterErrorCode.DuplicateStatus, exception.Errors[0].Code);
203             Assert.Equal("Status is a duplicate.", exception.Errors[0].Message);
204
205             Assert.Equal(0, mockHandler.QueueCount);
206         }
207
208         [Fact]
209         public async Task HandleTimeout_SuccessTest()
210         {
211             static async Task<int> AsyncFunc(CancellationToken token)
212             {
213                 await Task.Delay(10);
214                 token.ThrowIfCancellationRequested();
215                 return 1;
216             }
217
218             var timeout = TimeSpan.FromMilliseconds(200);
219             var ret = await TwitterApiConnection.HandleTimeout(AsyncFunc, timeout);
220
221             Assert.Equal(1, ret);
222         }
223
224         [Fact]
225         public async Task HandleTimeout_TimeoutTest()
226         {
227             var tcs = new TaskCompletionSource<bool>();
228
229             async Task<int> AsyncFunc(CancellationToken token)
230             {
231                 await Task.Delay(200);
232                 tcs.SetResult(token.IsCancellationRequested);
233                 return 1;
234             }
235
236             var timeout = TimeSpan.FromMilliseconds(10);
237             await Assert.ThrowsAsync<OperationCanceledException>(
238                 () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
239             );
240
241             var cancelRequested = await tcs.Task;
242             Assert.True(cancelRequested);
243         }
244
245         [Fact]
246         public async Task HandleTimeout_ThrowExceptionAfterTimeoutTest()
247         {
248             var tcs = new TaskCompletionSource<int>();
249
250             async Task<int> AsyncFunc(CancellationToken token)
251             {
252                 await Task.Delay(100);
253                 tcs.SetResult(1);
254                 throw new Exception();
255             }
256
257             var timeout = TimeSpan.FromMilliseconds(10);
258             await Assert.ThrowsAsync<OperationCanceledException>(
259                 () => TwitterApiConnection.HandleTimeout(AsyncFunc, timeout)
260             );
261
262             // キャンセル後に AsyncFunc で発生した例外が無視される(UnobservedTaskException イベントを発生させない)ことをチェックする
263             var error = false;
264             void UnobservedExceptionHandler(object s, UnobservedTaskExceptionEventArgs e)
265                 => error = true;
266
267             TaskScheduler.UnobservedTaskException += UnobservedExceptionHandler;
268
269             await tcs.Task;
270             await Task.Delay(10);
271             GC.Collect(); // UnobservedTaskException は Task のデストラクタで呼ばれるため強制的に GC を実行する
272             await Task.Delay(10);
273
274             Assert.False(error);
275
276             TaskScheduler.UnobservedTaskException -= UnobservedExceptionHandler;
277         }
278     }
279 }