OSDN Git Service

C# 8.0 のnull許容参照型を有効化
[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.Tasks;
33 using System.Web;
34 using Moq;
35 using OpenTween.Api;
36 using OpenTween.Api.DataModel;
37 using Xunit;
38
39 namespace OpenTween.Connection
40 {
41     public class TwitterApiConnectionTest
42     {
43         public TwitterApiConnectionTest()
44             => this.MyCommonSetup();
45
46         private void MyCommonSetup()
47         {
48             var mockAssembly = new Mock<_Assembly>();
49             mockAssembly.Setup(m => m.GetName()).Returns(new AssemblyName("OpenTween"));
50
51             MyCommon.EntryAssembly = mockAssembly.Object;
52         }
53
54         [Fact]
55         public async Task GetAsync_Test()
56         {
57             using var mockHandler = new HttpMessageHandlerMock();
58             using var http = new HttpClient(mockHandler);
59             using var apiConnection = new TwitterApiConnection("", "");
60             apiConnection.http = http;
61
62             mockHandler.Enqueue(x =>
63             {
64                 Assert.Equal(HttpMethod.Get, x.Method);
65                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
66                     x.RequestUri.GetLeftPart(UriPartial.Path));
67
68                 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
69
70                 Assert.Equal("1111", query["aaaa"]);
71                 Assert.Equal("2222", query["bbbb"]);
72
73                 return new HttpResponseMessage(HttpStatusCode.OK)
74                 {
75                     Content = new StringContent("\"hogehoge\""),
76                 };
77             });
78
79             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
80             var param = new Dictionary<string, string>
81             {
82                 ["aaaa"] = "1111",
83                 ["bbbb"] = "2222",
84             };
85
86             var result = await apiConnection.GetAsync<string>(endpoint, param, endpointName: "/hoge/tetete")
87                 .ConfigureAwait(false);
88             Assert.Equal("hogehoge", result);
89
90             Assert.Equal(0, mockHandler.QueueCount);
91         }
92
93         [Fact]
94         public async Task GetAsync_AbsoluteUriTest()
95         {
96             using var mockHandler = new HttpMessageHandlerMock();
97             using var http = new HttpClient(mockHandler);
98             using var apiConnection = new TwitterApiConnection("", "");
99             apiConnection.http = http;
100
101             mockHandler.Enqueue(x =>
102             {
103                 Assert.Equal(HttpMethod.Get, x.Method);
104                 Assert.Equal("http://example.com/hoge/tetete.json",
105                     x.RequestUri.GetLeftPart(UriPartial.Path));
106
107                 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
108
109                 Assert.Equal("1111", query["aaaa"]);
110                 Assert.Equal("2222", query["bbbb"]);
111
112                 return new HttpResponseMessage(HttpStatusCode.OK)
113                 {
114                     Content = new StringContent("\"hogehoge\""),
115                 };
116             });
117
118             var endpoint = new Uri("http://example.com/hoge/tetete.json", UriKind.Absolute);
119             var param = new Dictionary<string, string>
120             {
121                 ["aaaa"] = "1111",
122                 ["bbbb"] = "2222",
123             };
124
125             await apiConnection.GetAsync<string>(endpoint, param, endpointName: "/hoge/tetete")
126                 .ConfigureAwait(false);
127
128             Assert.Equal(0, mockHandler.QueueCount);
129         }
130
131         [Fact]
132         public async Task GetAsync_UpdateRateLimitTest()
133         {
134             using var mockHandler = new HttpMessageHandlerMock();
135             using var http = new HttpClient(mockHandler);
136             using var apiConnection = new TwitterApiConnection("", "");
137             apiConnection.http = http;
138
139             mockHandler.Enqueue(x =>
140             {
141                 Assert.Equal(HttpMethod.Get, x.Method);
142                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
143                     x.RequestUri.GetLeftPart(UriPartial.Path));
144
145                 return new HttpResponseMessage(HttpStatusCode.OK)
146                 {
147                     Headers =
148                     {
149                             { "X-Rate-Limit-Limit", "150" },
150                             { "X-Rate-Limit-Remaining", "100" },
151                             { "X-Rate-Limit-Reset", "1356998400" },
152                             { "X-Access-Level", "read-write-directmessages" },
153                     },
154                     Content = new StringContent("\"hogehoge\""),
155                 };
156             });
157
158             var apiStatus = new TwitterApiStatus();
159             MyCommon.TwitterApiInfo = apiStatus;
160
161             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
162
163             await apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete")
164                 .ConfigureAwait(false);
165
166             Assert.Equal(TwitterApiAccessLevel.ReadWriteAndDirectMessage, apiStatus.AccessLevel);
167             Assert.Equal(new ApiLimit(150, 100, new DateTimeUtc(2013, 1, 1, 0, 0, 0)), apiStatus.AccessLimit["/hoge/tetete"]);
168
169             Assert.Equal(0, mockHandler.QueueCount);
170         }
171
172         [Fact]
173         public async Task GetAsync_ErrorStatusTest()
174         {
175             using var mockHandler = new HttpMessageHandlerMock();
176             using var http = new HttpClient(mockHandler);
177             using var apiConnection = new TwitterApiConnection("", "");
178             apiConnection.http = http;
179
180             mockHandler.Enqueue(x =>
181             {
182                 return new HttpResponseMessage(HttpStatusCode.BadGateway)
183                 {
184                     Content = new StringContent("### Invalid JSON Response ###"),
185                 };
186             });
187
188             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
189
190             var exception = await Assert.ThrowsAsync<TwitterApiException>(() => apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete"))
191                 .ConfigureAwait(false);
192
193             // エラーレスポンスの読み込みに失敗した場合はステータスコードをそのままメッセージに使用する
194             Assert.Equal("BadGateway", exception.Message);
195             Assert.Null(exception.ErrorResponse);
196
197             Assert.Equal(0, mockHandler.QueueCount);
198         }
199
200         [Fact]
201         public async Task GetAsync_ErrorJsonTest()
202         {
203             using var mockHandler = new HttpMessageHandlerMock();
204             using var http = new HttpClient(mockHandler);
205             using var apiConnection = new TwitterApiConnection("", "");
206             apiConnection.http = http;
207
208             mockHandler.Enqueue(x =>
209             {
210                 return new HttpResponseMessage(HttpStatusCode.Forbidden)
211                 {
212                     Content = new StringContent("{\"errors\":[{\"code\":187,\"message\":\"Status is a duplicate.\"}]}"),
213                 };
214             });
215
216             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
217
218             var exception = await Assert.ThrowsAsync<TwitterApiException>(() => apiConnection.GetAsync<string>(endpoint, null, endpointName: "/hoge/tetete"))
219                 .ConfigureAwait(false);
220
221             // エラーレスポンスの JSON に含まれるエラーコードに基づいてメッセージを出力する
222             Assert.Equal("DuplicateStatus", exception.Message);
223
224             Assert.Equal(TwitterErrorCode.DuplicateStatus, exception.Errors[0].Code);
225             Assert.Equal("Status is a duplicate.", exception.Errors[0].Message);
226
227             Assert.Equal(0, mockHandler.QueueCount);
228         }
229
230         [Fact]
231         public async Task GetStreamAsync_Test()
232         {
233             using var mockHandler = new HttpMessageHandlerMock();
234             using var http = new HttpClient(mockHandler);
235             using var apiConnection = new TwitterApiConnection("", "");
236             using var image = TestUtils.CreateDummyImage();
237             apiConnection.http = http;
238
239             mockHandler.Enqueue(x =>
240             {
241                 Assert.Equal(HttpMethod.Get, x.Method);
242                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
243                     x.RequestUri.GetLeftPart(UriPartial.Path));
244
245                 var query = HttpUtility.ParseQueryString(x.RequestUri.Query);
246
247                 Assert.Equal("1111", query["aaaa"]);
248                 Assert.Equal("2222", query["bbbb"]);
249
250                 return new HttpResponseMessage(HttpStatusCode.OK)
251                 {
252                     Content = new ByteArrayContent(image.Stream.ToArray()),
253                 };
254             });
255
256             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
257             var param = new Dictionary<string, string>
258             {
259                 ["aaaa"] = "1111",
260                 ["bbbb"] = "2222",
261             };
262
263             var stream = await apiConnection.GetStreamAsync(endpoint, param)
264                 .ConfigureAwait(false);
265
266             using (var memoryStream = new MemoryStream())
267             {
268                 // 内容の比較のために MemoryStream にコピー
269                 await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
270
271                 Assert.Equal(image.Stream.ToArray(), memoryStream.ToArray());
272             }
273
274             Assert.Equal(0, mockHandler.QueueCount);
275         }
276
277         [Fact]
278         public async Task PostLazyAsync_Test()
279         {
280             using var mockHandler = new HttpMessageHandlerMock();
281             using var http = new HttpClient(mockHandler);
282             using var apiConnection = new TwitterApiConnection("", "");
283             apiConnection.http = http;
284
285             mockHandler.Enqueue(async x =>
286             {
287                 Assert.Equal(HttpMethod.Post, x.Method);
288                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
289                     x.RequestUri.AbsoluteUri);
290
291                 var body = await x.Content.ReadAsStringAsync()
292                     .ConfigureAwait(false);
293                 var query = HttpUtility.ParseQueryString(body);
294
295                 Assert.Equal("1111", query["aaaa"]);
296                 Assert.Equal("2222", query["bbbb"]);
297
298                 return new HttpResponseMessage(HttpStatusCode.OK)
299                 {
300                     Content = new StringContent("\"hogehoge\""),
301                 };
302             });
303
304             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
305             var param = new Dictionary<string, string>
306             {
307                 ["aaaa"] = "1111",
308                 ["bbbb"] = "2222",
309             };
310
311             var result = await apiConnection.PostLazyAsync<string>(endpoint, param)
312                 .ConfigureAwait(false);
313
314             Assert.Equal("hogehoge", await result.LoadJsonAsync()
315                 .ConfigureAwait(false));
316
317             Assert.Equal(0, mockHandler.QueueCount);
318         }
319
320         [Fact]
321         public async Task PostLazyAsync_MultipartTest()
322         {
323             using var mockHandler = new HttpMessageHandlerMock();
324             using var http = new HttpClient(mockHandler);
325             using var apiConnection = new TwitterApiConnection("", "");
326             apiConnection.httpUpload = http;
327
328             using var image = TestUtils.CreateDummyImage();
329             using var media = new MemoryImageMediaItem(image);
330
331             mockHandler.Enqueue(async x =>
332             {
333                 Assert.Equal(HttpMethod.Post, x.Method);
334                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
335                     x.RequestUri.AbsoluteUri);
336
337                 Assert.IsType<MultipartFormDataContent>(x.Content);
338
339                 var boundary = x.Content.Headers.ContentType.Parameters.Cast<NameValueHeaderValue>()
340                     .First(y => y.Name == "boundary").Value;
341
342                     // 前後のダブルクオーテーションを除去
343                     boundary = boundary.Substring(1, boundary.Length - 2);
344
345                 var expectedText =
346                     $"--{boundary}\r\n" +
347                     "Content-Type: text/plain; charset=utf-8\r\n" +
348                     "Content-Disposition: form-data; name=aaaa\r\n" +
349                     "\r\n" +
350                     "1111\r\n" +
351                     $"--{boundary}\r\n" +
352                     "Content-Type: text/plain; charset=utf-8\r\n" +
353                     "Content-Disposition: form-data; name=bbbb\r\n" +
354                     "\r\n" +
355                     "2222\r\n" +
356                     $"--{boundary}\r\n" +
357                     $"Content-Disposition: form-data; name=media1; filename={media.Name}; filename*=utf-8''{media.Name}\r\n" +
358                     "\r\n";
359
360                 var expected = Encoding.UTF8.GetBytes(expectedText)
361                     .Concat(image.Stream.ToArray())
362                     .Concat(Encoding.UTF8.GetBytes($"\r\n--{boundary}--\r\n"));
363
364                 Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
365
366                 return new HttpResponseMessage(HttpStatusCode.OK)
367                 {
368                     Content = new StringContent("\"hogehoge\""),
369                 };
370             });
371
372             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
373             var param = new Dictionary<string, string>
374             {
375                 ["aaaa"] = "1111",
376                 ["bbbb"] = "2222",
377             };
378             var mediaParam = new Dictionary<string, IMediaItem>
379             {
380                 ["media1"] = media,
381             };
382
383             var result = await apiConnection.PostLazyAsync<string>(endpoint, param, mediaParam)
384                 .ConfigureAwait(false);
385
386             Assert.Equal("hogehoge", await result.LoadJsonAsync()
387                 .ConfigureAwait(false));
388
389             Assert.Equal(0, mockHandler.QueueCount);
390         }
391
392         [Fact]
393         public async Task PostLazyAsync_Multipart_NullTest()
394         {
395             using var mockHandler = new HttpMessageHandlerMock();
396             using var http = new HttpClient(mockHandler);
397             using var apiConnection = new TwitterApiConnection("", "");
398             apiConnection.httpUpload = http;
399
400             mockHandler.Enqueue(async x =>
401             {
402                 Assert.Equal(HttpMethod.Post, x.Method);
403                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
404                     x.RequestUri.AbsoluteUri);
405
406                 Assert.IsType<MultipartFormDataContent>(x.Content);
407
408                 var boundary = x.Content.Headers.ContentType.Parameters.Cast<NameValueHeaderValue>()
409                     .First(y => y.Name == "boundary").Value;
410
411                     // 前後のダブルクオーテーションを除去
412                     boundary = boundary.Substring(1, boundary.Length - 2);
413
414                 var expectedText =
415                     $"--{boundary}\r\n" +
416                     $"\r\n--{boundary}--\r\n";
417
418                 var expected = Encoding.UTF8.GetBytes(expectedText);
419
420                 Assert.Equal(expected, await x.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
421
422                 return new HttpResponseMessage(HttpStatusCode.OK)
423                 {
424                     Content = new StringContent("\"hogehoge\""),
425                 };
426             });
427
428             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
429
430             var result = await apiConnection.PostLazyAsync<string>(endpoint, param: null, media: null)
431                 .ConfigureAwait(false);
432
433             Assert.Equal("hogehoge", await result.LoadJsonAsync()
434                 .ConfigureAwait(false));
435
436             Assert.Equal(0, mockHandler.QueueCount);
437         }
438
439         [Fact]
440         public async Task PostJsonAsync_Test()
441         {
442             using var mockHandler = new HttpMessageHandlerMock();
443             using var http = new HttpClient(mockHandler);
444             using var apiConnection = new TwitterApiConnection("", "");
445             apiConnection.http = http;
446
447             mockHandler.Enqueue(async x =>
448             {
449                 Assert.Equal(HttpMethod.Post, x.Method);
450                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
451                     x.RequestUri.AbsoluteUri);
452
453                 Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString());
454
455                 var body = await x.Content.ReadAsStringAsync()
456                     .ConfigureAwait(false);
457
458                 Assert.Equal("{\"aaaa\": 1111}", body);
459
460                 return new HttpResponseMessage(HttpStatusCode.NoContent);
461             });
462
463             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
464
465             await apiConnection.PostJsonAsync(endpoint, "{\"aaaa\": 1111}")
466                 .ConfigureAwait(false);
467
468             Assert.Equal(0, mockHandler.QueueCount);
469         }
470
471         [Fact]
472         public async Task PostJsonAsync_T_Test()
473         {
474             using var mockHandler = new HttpMessageHandlerMock();
475             using var http = new HttpClient(mockHandler);
476             using var apiConnection = new TwitterApiConnection("", "");
477             apiConnection.http = http;
478
479             mockHandler.Enqueue(async x =>
480             {
481                 Assert.Equal(HttpMethod.Post, x.Method);
482                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
483                     x.RequestUri.AbsoluteUri);
484
485                 Assert.Equal("application/json; charset=utf-8", x.Content.Headers.ContentType.ToString());
486
487                 var body = await x.Content.ReadAsStringAsync()
488                     .ConfigureAwait(false);
489
490                 Assert.Equal("{\"aaaa\": 1111}", body);
491
492                 return new HttpResponseMessage(HttpStatusCode.OK)
493                 {
494                     Content = new StringContent("\"hogehoge\""),
495                 };
496             });
497
498             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
499
500             var response = await apiConnection.PostJsonAsync<string>(endpoint, "{\"aaaa\": 1111}")
501                 .ConfigureAwait(false);
502
503             var result = await response.LoadJsonAsync()
504                 .ConfigureAwait(false);
505
506             Assert.Equal("hogehoge", result);
507
508             Assert.Equal(0, mockHandler.QueueCount);
509         }
510
511         [Fact]
512         public async Task DeleteAsync_Test()
513         {
514             using var mockHandler = new HttpMessageHandlerMock();
515             using var http = new HttpClient(mockHandler);
516             using var apiConnection = new TwitterApiConnection("", "");
517             apiConnection.http = http;
518
519             mockHandler.Enqueue(x =>
520             {
521                 Assert.Equal(HttpMethod.Delete, x.Method);
522                 Assert.Equal("https://api.twitter.com/1.1/hoge/tetete.json",
523                     x.RequestUri.AbsoluteUri);
524
525                 return new HttpResponseMessage(HttpStatusCode.NoContent);
526             });
527
528             var endpoint = new Uri("hoge/tetete.json", UriKind.Relative);
529
530             await apiConnection.DeleteAsync(endpoint)
531                 .ConfigureAwait(false);
532
533             Assert.Equal(0, mockHandler.QueueCount);
534         }
535     }
536 }