OSDN Git Service

TwitterApiTestでリクエストを検証するモックを生成するためのヘルパーを追加
[opentween/open-tween.git] / OpenTween.Tests / Api / TwitterApiTest.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.Reflection;
29 using System.Runtime.InteropServices;
30 using System.Text;
31 using System.Threading.Tasks;
32 using Moq;
33 using OpenTween.Api.DataModel;
34 using OpenTween.Connection;
35 using Xunit;
36
37 namespace OpenTween.Api
38 {
39     public class TwitterApiTest
40     {
41         public TwitterApiTest()
42             => this.MyCommonSetup();
43
44         private void MyCommonSetup()
45         {
46             var mockAssembly = new Mock<_Assembly>();
47             mockAssembly.Setup(m => m.GetName()).Returns(new AssemblyName("OpenTween"));
48
49             MyCommon.EntryAssembly = mockAssembly.Object;
50         }
51
52         [Fact]
53         public void Initialize_Test()
54         {
55             using var twitterApi = new TwitterApi();
56             var apiConnection = Assert.IsType<TwitterApiConnection>(twitterApi.Connection);
57             Assert.IsType<TwitterCredentialNone>(apiConnection.Credential);
58
59             var credential = new TwitterCredentialOAuth1(TwitterAppToken.GetDefault(), "*** AccessToken ***", "*** AccessSecret ***");
60             twitterApi.Initialize(credential, userId: 100L, screenName: "hogehoge");
61
62             apiConnection = Assert.IsType<TwitterApiConnection>(twitterApi.Connection);
63             Assert.Same(credential, apiConnection.Credential);
64
65             Assert.Equal(100L, twitterApi.CurrentUserId);
66             Assert.Equal("hogehoge", twitterApi.CurrentScreenName);
67
68             // 複数回 Initialize を実行した場合は新たに TwitterApiConnection が生成される
69             var credential2 = new TwitterCredentialOAuth1(TwitterAppToken.GetDefault(), "*** AccessToken2 ***", "*** AccessSecret2 ***");
70             twitterApi.Initialize(credential2, userId: 200L, screenName: "foobar");
71
72             var oldApiConnection = apiConnection;
73             Assert.True(oldApiConnection.IsDisposed);
74
75             apiConnection = Assert.IsType<TwitterApiConnection>(twitterApi.Connection);
76             Assert.Same(credential2, apiConnection.Credential);
77
78             Assert.Equal(200L, twitterApi.CurrentUserId);
79             Assert.Equal("foobar", twitterApi.CurrentScreenName);
80         }
81
82         private Mock<IApiConnectionLegacy> CreateApiConnectionMock<T>(Action<T> verifyRequest)
83             where T : IHttpRequest
84         {
85             Func<T, bool> verifyRequestWrapper = r =>
86             {
87                 verifyRequest(r);
88                 // Assert メソッドを使用する想定のため、失敗した場合は例外が発生しここまで到達しない
89                 return true;
90             };
91
92             var responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
93             var mock = new Mock<IApiConnectionLegacy>();
94             mock.Setup(x =>
95                 x.SendAsync(
96                     It.Is<T>(r => verifyRequestWrapper(r))
97                 )
98             )
99             .ReturnsAsync(new ApiResponse(responseMessage));
100
101             return mock;
102         }
103
104         [Fact]
105         public async Task StatusesHomeTimeline_Test()
106         {
107             var mock = new Mock<IApiConnectionLegacy>();
108             mock.Setup(x =>
109                 x.GetAsync<TwitterStatus[]>(
110                     new Uri("statuses/home_timeline.json", UriKind.Relative),
111                     new Dictionary<string, string>
112                     {
113                             { "include_entities", "true" },
114                             { "include_ext_alt_text", "true" },
115                             { "tweet_mode", "extended" },
116                             { "count", "200" },
117                             { "max_id", "900" },
118                             { "since_id", "100" },
119                     },
120                     "/statuses/home_timeline")
121             )
122             .ReturnsAsync(Array.Empty<TwitterStatus>());
123
124             using var twitterApi = new TwitterApi();
125             twitterApi.ApiConnection = mock.Object;
126
127             await twitterApi.StatusesHomeTimeline(200, maxId: new("900"), sinceId: new("100"));
128
129             mock.VerifyAll();
130         }
131
132         [Fact]
133         public async Task StatusesMentionsTimeline_Test()
134         {
135             var mock = new Mock<IApiConnectionLegacy>();
136             mock.Setup(x =>
137                 x.GetAsync<TwitterStatus[]>(
138                     new Uri("statuses/mentions_timeline.json", UriKind.Relative),
139                     new Dictionary<string, string>
140                     {
141                             { "include_entities", "true" },
142                             { "include_ext_alt_text", "true" },
143                             { "tweet_mode", "extended" },
144                             { "count", "200" },
145                             { "max_id", "900" },
146                             { "since_id", "100" },
147                     },
148                     "/statuses/mentions_timeline")
149             )
150             .ReturnsAsync(Array.Empty<TwitterStatus>());
151
152             using var twitterApi = new TwitterApi();
153             twitterApi.ApiConnection = mock.Object;
154
155             await twitterApi.StatusesMentionsTimeline(200, maxId: new("900"), sinceId: new("100"));
156
157             mock.VerifyAll();
158         }
159
160         [Fact]
161         public async Task StatusesUserTimeline_Test()
162         {
163             var mock = new Mock<IApiConnectionLegacy>();
164             mock.Setup(x =>
165                 x.GetAsync<TwitterStatus[]>(
166                     new Uri("statuses/user_timeline.json", UriKind.Relative),
167                     new Dictionary<string, string>
168                     {
169                             { "screen_name", "twitterapi" },
170                             { "include_rts", "true" },
171                             { "include_entities", "true" },
172                             { "include_ext_alt_text", "true" },
173                             { "tweet_mode", "extended" },
174                             { "count", "200" },
175                             { "max_id", "900" },
176                             { "since_id", "100" },
177                     },
178                     "/statuses/user_timeline")
179             )
180             .ReturnsAsync(Array.Empty<TwitterStatus>());
181
182             using var twitterApi = new TwitterApi();
183             twitterApi.ApiConnection = mock.Object;
184
185             await twitterApi.StatusesUserTimeline("twitterapi", count: 200, maxId: new("900"), sinceId: new("100"));
186
187             mock.VerifyAll();
188         }
189
190         [Fact]
191         public async Task StatusesShow_Test()
192         {
193             var mock = new Mock<IApiConnectionLegacy>();
194             mock.Setup(x =>
195                 x.GetAsync<TwitterStatus>(
196                     new Uri("statuses/show.json", UriKind.Relative),
197                     new Dictionary<string, string>
198                     {
199                             { "id", "100" },
200                             { "include_entities", "true" },
201                             { "include_ext_alt_text", "true" },
202                             { "tweet_mode", "extended" },
203                     },
204                     "/statuses/show/:id")
205             )
206             .ReturnsAsync(new TwitterStatus { Id = 100L });
207
208             using var twitterApi = new TwitterApi();
209             twitterApi.ApiConnection = mock.Object;
210
211             await twitterApi.StatusesShow(statusId: new("100"));
212
213             mock.VerifyAll();
214         }
215
216         [Fact]
217         public async Task StatusesLookup_Test()
218         {
219             var mock = new Mock<IApiConnectionLegacy>();
220             mock.Setup(x =>
221                 x.GetAsync<TwitterStatus[]>(
222                     new Uri("statuses/lookup.json", UriKind.Relative),
223                     new Dictionary<string, string>
224                     {
225                         { "id", "100,200" },
226                         { "include_entities", "true" },
227                         { "include_ext_alt_text", "true" },
228                         { "tweet_mode", "extended" },
229                     },
230                     "/statuses/lookup"
231                 )
232             )
233             .ReturnsAsync(Array.Empty<TwitterStatus>());
234
235             using var twitterApi = new TwitterApi();
236             twitterApi.ApiConnection = mock.Object;
237
238             await twitterApi.StatusesLookup(statusIds: new[] { "100", "200" });
239
240             mock.VerifyAll();
241         }
242
243         [Fact]
244         public async Task StatusesUpdate_Test()
245         {
246             var mock = new Mock<IApiConnectionLegacy>();
247             mock.Setup(x =>
248                 x.PostLazyAsync<TwitterStatus>(
249                     new Uri("statuses/update.json", UriKind.Relative),
250                     new Dictionary<string, string>
251                     {
252                             { "status", "hogehoge" },
253                             { "include_entities", "true" },
254                             { "include_ext_alt_text", "true" },
255                             { "tweet_mode", "extended" },
256                             { "in_reply_to_status_id", "100" },
257                             { "media_ids", "10,20" },
258                             { "auto_populate_reply_metadata", "true" },
259                             { "exclude_reply_user_ids", "100,200" },
260                             { "attachment_url", "https://twitter.com/twitterapi/status/22634515958" },
261                     })
262             )
263             .ReturnsAsync(LazyJson.Create(new TwitterStatus()));
264
265             using var twitterApi = new TwitterApi();
266             twitterApi.ApiConnection = mock.Object;
267
268             await twitterApi.StatusesUpdate(
269                     "hogehoge",
270                     replyToId: new("100"),
271                     mediaIds: new[] { 10L, 20L },
272                     autoPopulateReplyMetadata: true,
273                     excludeReplyUserIds: new[] { 100L, 200L },
274                     attachmentUrl: "https://twitter.com/twitterapi/status/22634515958"
275                 )
276                 .IgnoreResponse();
277
278             mock.VerifyAll();
279         }
280
281         [Fact]
282         public async Task StatusesUpdate_ExcludeReplyUserIdsEmptyTest()
283         {
284             var mock = new Mock<IApiConnectionLegacy>();
285             mock.Setup(x =>
286                 x.PostLazyAsync<TwitterStatus>(
287                     new Uri("statuses/update.json", UriKind.Relative),
288                     new Dictionary<string, string>
289                     {
290                         { "status", "hogehoge" },
291                         { "include_entities", "true" },
292                         { "include_ext_alt_text", "true" },
293                         { "tweet_mode", "extended" },
294                         // exclude_reply_user_ids は空の場合には送信されない
295                     })
296             )
297             .ReturnsAsync(LazyJson.Create(new TwitterStatus()));
298
299             using var twitterApi = new TwitterApi();
300             twitterApi.ApiConnection = mock.Object;
301
302             await twitterApi.StatusesUpdate("hogehoge", replyToId: null, mediaIds: null, excludeReplyUserIds: Array.Empty<long>())
303                 .IgnoreResponse();
304
305             mock.VerifyAll();
306         }
307
308         [Fact]
309         public async Task StatusesDestroy_Test()
310         {
311             var mock = new Mock<IApiConnectionLegacy>();
312             mock.Setup(x =>
313                 x.PostLazyAsync<TwitterStatus>(
314                     new Uri("statuses/destroy.json", UriKind.Relative),
315                     new Dictionary<string, string> { { "id", "100" } })
316             )
317             .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L }));
318
319             using var twitterApi = new TwitterApi();
320             twitterApi.ApiConnection = mock.Object;
321
322             await twitterApi.StatusesDestroy(statusId: new("100"))
323                 .IgnoreResponse();
324
325             mock.VerifyAll();
326         }
327
328         [Fact]
329         public async Task StatusesRetweet_Test()
330         {
331             var mock = new Mock<IApiConnectionLegacy>();
332             mock.Setup(x =>
333                 x.PostLazyAsync<TwitterStatus>(
334                     new Uri("statuses/retweet.json", UriKind.Relative),
335                     new Dictionary<string, string>
336                     {
337                             { "id", "100" },
338                             { "include_entities", "true" },
339                             { "include_ext_alt_text", "true" },
340                             { "tweet_mode", "extended" },
341                     })
342             )
343             .ReturnsAsync(LazyJson.Create(new TwitterStatus()));
344
345             using var twitterApi = new TwitterApi();
346             twitterApi.ApiConnection = mock.Object;
347
348             await twitterApi.StatusesRetweet(new("100"))
349                 .IgnoreResponse();
350
351             mock.VerifyAll();
352         }
353
354         [Fact]
355         public async Task SearchTweets_Test()
356         {
357             var mock = new Mock<IApiConnectionLegacy>();
358             mock.Setup(x =>
359                 x.GetAsync<TwitterSearchResult>(
360                     new Uri("search/tweets.json", UriKind.Relative),
361                     new Dictionary<string, string>
362                     {
363                             { "q", "from:twitterapi" },
364                             { "result_type", "recent" },
365                             { "include_entities", "true" },
366                             { "include_ext_alt_text", "true" },
367                             { "tweet_mode", "extended" },
368                             { "lang", "en" },
369                             { "count", "200" },
370                             { "max_id", "900" },
371                             { "since_id", "100" },
372                     },
373                     "/search/tweets")
374             )
375             .ReturnsAsync(new TwitterSearchResult());
376
377             using var twitterApi = new TwitterApi();
378             twitterApi.ApiConnection = mock.Object;
379
380             await twitterApi.SearchTweets("from:twitterapi", "en", count: 200, maxId: new("900"), sinceId: new("100"));
381
382             mock.VerifyAll();
383         }
384
385         [Fact]
386         public async Task ListsOwnerships_Test()
387         {
388             var mock = new Mock<IApiConnectionLegacy>();
389             mock.Setup(x =>
390                 x.GetAsync<TwitterLists>(
391                     new Uri("lists/ownerships.json", UriKind.Relative),
392                     new Dictionary<string, string>
393                     {
394                             { "screen_name", "twitterapi" },
395                             { "cursor", "-1" },
396                             { "count", "100" },
397                     },
398                     "/lists/ownerships")
399             )
400             .ReturnsAsync(new TwitterLists());
401
402             using var twitterApi = new TwitterApi();
403             twitterApi.ApiConnection = mock.Object;
404
405             await twitterApi.ListsOwnerships("twitterapi", cursor: -1L, count: 100);
406
407             mock.VerifyAll();
408         }
409
410         [Fact]
411         public async Task ListsSubscriptions_Test()
412         {
413             var mock = new Mock<IApiConnectionLegacy>();
414             mock.Setup(x =>
415                 x.GetAsync<TwitterLists>(
416                     new Uri("lists/subscriptions.json", UriKind.Relative),
417                     new Dictionary<string, string>
418                     {
419                             { "screen_name", "twitterapi" },
420                             { "cursor", "-1" },
421                             { "count", "100" },
422                     },
423                     "/lists/subscriptions")
424             )
425             .ReturnsAsync(new TwitterLists());
426
427             using var twitterApi = new TwitterApi();
428             twitterApi.ApiConnection = mock.Object;
429
430             await twitterApi.ListsSubscriptions("twitterapi", cursor: -1L, count: 100);
431
432             mock.VerifyAll();
433         }
434
435         [Fact]
436         public async Task ListsMemberships_Test()
437         {
438             var mock = new Mock<IApiConnectionLegacy>();
439             mock.Setup(x =>
440                 x.GetAsync<TwitterLists>(
441                     new Uri("lists/memberships.json", UriKind.Relative),
442                     new Dictionary<string, string>
443                     {
444                             { "screen_name", "twitterapi" },
445                             { "cursor", "-1" },
446                             { "count", "100" },
447                             { "filter_to_owned_lists", "true" },
448                     },
449                     "/lists/memberships")
450             )
451             .ReturnsAsync(new TwitterLists());
452
453             using var twitterApi = new TwitterApi();
454             twitterApi.ApiConnection = mock.Object;
455
456             await twitterApi.ListsMemberships("twitterapi", cursor: -1L, count: 100, filterToOwnedLists: true);
457
458             mock.VerifyAll();
459         }
460
461         [Fact]
462         public async Task ListsCreate_Test()
463         {
464             var mock = new Mock<IApiConnectionLegacy>();
465             mock.Setup(x =>
466                 x.PostLazyAsync<TwitterList>(
467                     new Uri("lists/create.json", UriKind.Relative),
468                     new Dictionary<string, string>
469                     {
470                             { "name", "hogehoge" },
471                             { "description", "aaaa" },
472                             { "mode", "private" },
473                     })
474             )
475             .ReturnsAsync(LazyJson.Create(new TwitterList()));
476
477             using var twitterApi = new TwitterApi();
478             twitterApi.ApiConnection = mock.Object;
479
480             await twitterApi.ListsCreate("hogehoge", description: "aaaa", @private: true)
481                 .IgnoreResponse();
482
483             mock.VerifyAll();
484         }
485
486         [Fact]
487         public async Task ListsUpdate_Test()
488         {
489             var mock = new Mock<IApiConnectionLegacy>();
490             mock.Setup(x =>
491                 x.PostLazyAsync<TwitterList>(
492                     new Uri("lists/update.json", UriKind.Relative),
493                     new Dictionary<string, string>
494                     {
495                             { "list_id", "12345" },
496                             { "name", "hogehoge" },
497                             { "description", "aaaa" },
498                             { "mode", "private" },
499                     })
500             )
501             .ReturnsAsync(LazyJson.Create(new TwitterList()));
502
503             using var twitterApi = new TwitterApi();
504             twitterApi.ApiConnection = mock.Object;
505
506             await twitterApi.ListsUpdate(12345L, name: "hogehoge", description: "aaaa", @private: true)
507                 .IgnoreResponse();
508
509             mock.VerifyAll();
510         }
511
512         [Fact]
513         public async Task ListsDestroy_Test()
514         {
515             var mock = new Mock<IApiConnectionLegacy>();
516             mock.Setup(x =>
517                 x.PostLazyAsync<TwitterList>(
518                     new Uri("lists/destroy.json", UriKind.Relative),
519                     new Dictionary<string, string>
520                     {
521                             { "list_id", "12345" },
522                     })
523             )
524             .ReturnsAsync(LazyJson.Create(new TwitterList()));
525
526             using var twitterApi = new TwitterApi();
527             twitterApi.ApiConnection = mock.Object;
528
529             await twitterApi.ListsDestroy(12345L)
530                 .IgnoreResponse();
531
532             mock.VerifyAll();
533         }
534
535         [Fact]
536         public async Task ListsStatuses_Test()
537         {
538             var mock = new Mock<IApiConnectionLegacy>();
539             mock.Setup(x =>
540                 x.GetAsync<TwitterStatus[]>(
541                     new Uri("lists/statuses.json", UriKind.Relative),
542                     new Dictionary<string, string>
543                     {
544                             { "list_id", "12345" },
545                             { "include_entities", "true" },
546                             { "include_ext_alt_text", "true" },
547                             { "tweet_mode", "extended" },
548                             { "count", "200" },
549                             { "max_id", "900" },
550                             { "since_id", "100" },
551                             { "include_rts", "true" },
552                     },
553                     "/lists/statuses")
554             )
555             .ReturnsAsync(Array.Empty<TwitterStatus>());
556
557             using var twitterApi = new TwitterApi();
558             twitterApi.ApiConnection = mock.Object;
559
560             await twitterApi.ListsStatuses(12345L, count: 200, maxId: new("900"), sinceId: new("100"), includeRTs: true);
561
562             mock.VerifyAll();
563         }
564
565         [Fact]
566         public async Task ListsMembers_Test()
567         {
568             var mock = new Mock<IApiConnectionLegacy>();
569             mock.Setup(x =>
570                 x.GetAsync<TwitterUsers>(
571                     new Uri("lists/members.json", UriKind.Relative),
572                     new Dictionary<string, string>
573                     {
574                             { "list_id", "12345" },
575                             { "include_entities", "true" },
576                             { "include_ext_alt_text", "true" },
577                             { "tweet_mode", "extended" },
578                             { "cursor", "-1" },
579                     },
580                     "/lists/members")
581             )
582             .ReturnsAsync(new TwitterUsers());
583
584             using var twitterApi = new TwitterApi();
585             twitterApi.ApiConnection = mock.Object;
586
587             await twitterApi.ListsMembers(12345L, cursor: -1);
588
589             mock.VerifyAll();
590         }
591
592         [Fact]
593         public async Task ListsMembersShow_Test()
594         {
595             var mock = new Mock<IApiConnectionLegacy>();
596             mock.Setup(x =>
597                 x.GetAsync<TwitterUser>(
598                     new Uri("lists/members/show.json", UriKind.Relative),
599                     new Dictionary<string, string>
600                     {
601                             { "list_id", "12345" },
602                             { "screen_name", "twitterapi" },
603                             { "include_entities", "true" },
604                             { "include_ext_alt_text", "true" },
605                             { "tweet_mode", "extended" },
606                     },
607                     "/lists/members/show")
608             )
609             .ReturnsAsync(new TwitterUser());
610
611             using var twitterApi = new TwitterApi();
612             twitterApi.ApiConnection = mock.Object;
613
614             await twitterApi.ListsMembersShow(12345L, "twitterapi");
615
616             mock.VerifyAll();
617         }
618
619         [Fact]
620         public async Task ListsMembersCreate_Test()
621         {
622             var mock = new Mock<IApiConnectionLegacy>();
623             mock.Setup(x =>
624                 x.PostLazyAsync<TwitterUser>(
625                     new Uri("lists/members/create.json", UriKind.Relative),
626                     new Dictionary<string, string>
627                     {
628                             { "list_id", "12345" },
629                             { "screen_name", "twitterapi" },
630                             { "include_entities", "true" },
631                             { "include_ext_alt_text", "true" },
632                             { "tweet_mode", "extended" },
633                     })
634             )
635             .ReturnsAsync(LazyJson.Create(new TwitterUser()));
636
637             using var twitterApi = new TwitterApi();
638             twitterApi.ApiConnection = mock.Object;
639
640             await twitterApi.ListsMembersCreate(12345L, "twitterapi")
641                 .IgnoreResponse();
642
643             mock.VerifyAll();
644         }
645
646         [Fact]
647         public async Task ListsMembersDestroy_Test()
648         {
649             var mock = new Mock<IApiConnectionLegacy>();
650             mock.Setup(x =>
651                 x.PostLazyAsync<TwitterUser>(
652                     new Uri("lists/members/destroy.json", UriKind.Relative),
653                     new Dictionary<string, string>
654                     {
655                             { "list_id", "12345" },
656                             { "screen_name", "twitterapi" },
657                             { "include_entities", "true" },
658                             { "include_ext_alt_text", "true" },
659                             { "tweet_mode", "extended" },
660                     })
661             )
662             .ReturnsAsync(LazyJson.Create(new TwitterUser()));
663
664             using var twitterApi = new TwitterApi();
665             twitterApi.ApiConnection = mock.Object;
666
667             await twitterApi.ListsMembersDestroy(12345L, "twitterapi")
668                 .IgnoreResponse();
669
670             mock.VerifyAll();
671         }
672
673         [Fact]
674         public async Task DirectMessagesEventsList_Test()
675         {
676             var mock = new Mock<IApiConnectionLegacy>();
677             mock.Setup(x =>
678                 x.GetAsync<TwitterMessageEventList>(
679                     new Uri("direct_messages/events/list.json", UriKind.Relative),
680                     new Dictionary<string, string>
681                     {
682                             { "count", "50" },
683                             { "cursor", "12345abcdefg" },
684                     },
685                     "/direct_messages/events/list")
686             )
687             .ReturnsAsync(new TwitterMessageEventList());
688
689             using var twitterApi = new TwitterApi();
690             twitterApi.ApiConnection = mock.Object;
691
692             await twitterApi.DirectMessagesEventsList(count: 50, cursor: "12345abcdefg");
693
694             mock.VerifyAll();
695         }
696
697         [Fact]
698         public async Task DirectMessagesEventsNew_Test()
699         {
700             var requestJson = """
701                 {
702                   "event": {
703                     "type": "message_create",
704                     "message_create": {
705                       "target": {
706                         "recipient_id": "12345"
707                       },
708                       "message_data": {
709                         "text": "hogehoge",
710                         "attachment": {
711                           "type": "media",
712                           "media": {
713                             "id": "67890"
714                           }
715                         }
716                       }
717                     }
718                   }
719                 }
720                 """;
721
722             var mock = this.CreateApiConnectionMock<PostJsonRequest>(r =>
723             {
724                 Assert.Equal(new("direct_messages/events/new.json", UriKind.Relative), r.RequestUri);
725                 Assert.Equal(requestJson, r.JsonString);
726             });
727
728             using var twitterApi = new TwitterApi();
729             twitterApi.ApiConnection = mock.Object;
730
731             await twitterApi.DirectMessagesEventsNew(recipientId: 12345L, text: "hogehoge", mediaId: 67890L);
732
733             mock.VerifyAll();
734         }
735
736         [Fact]
737         public async Task DirectMessagesEventsDestroy_Test()
738         {
739             var mock = this.CreateApiConnectionMock<DeleteRequest>(r =>
740             {
741                 Assert.Equal(new("direct_messages/events/destroy.json", UriKind.Relative), r.RequestUri);
742                 var expectedQuery = new Dictionary<string, string>
743                 {
744                     ["id"] = "100",
745                 };
746                 Assert.Equal(expectedQuery, r.Query);
747             });
748
749             using var twitterApi = new TwitterApi();
750             twitterApi.ApiConnection = mock.Object;
751
752             await twitterApi.DirectMessagesEventsDestroy(eventId: new("100"));
753
754             mock.VerifyAll();
755         }
756
757         [Fact]
758         public async Task UsersShow_Test()
759         {
760             var mock = new Mock<IApiConnectionLegacy>();
761             mock.Setup(x =>
762                 x.GetAsync<TwitterUser>(
763                     new Uri("users/show.json", UriKind.Relative),
764                     new Dictionary<string, string>
765                     {
766                             { "screen_name", "twitterapi" },
767                             { "include_entities", "true" },
768                             { "include_ext_alt_text", "true" },
769                             { "tweet_mode", "extended" },
770                     },
771                     "/users/show/:id")
772             )
773             .ReturnsAsync(new TwitterUser { ScreenName = "twitterapi" });
774
775             using var twitterApi = new TwitterApi();
776             twitterApi.ApiConnection = mock.Object;
777
778             await twitterApi.UsersShow(screenName: "twitterapi");
779
780             mock.VerifyAll();
781         }
782
783         [Fact]
784         public async Task UsersLookup_Test()
785         {
786             var mock = new Mock<IApiConnectionLegacy>();
787             mock.Setup(x =>
788                 x.GetAsync<TwitterUser[]>(
789                     new Uri("users/lookup.json", UriKind.Relative),
790                     new Dictionary<string, string>
791                     {
792                             { "user_id", "11111,22222" },
793                             { "include_entities", "true" },
794                             { "include_ext_alt_text", "true" },
795                             { "tweet_mode", "extended" },
796                     },
797                     "/users/lookup")
798             )
799             .ReturnsAsync(Array.Empty<TwitterUser>());
800
801             using var twitterApi = new TwitterApi();
802             twitterApi.ApiConnection = mock.Object;
803
804             await twitterApi.UsersLookup(userIds: new[] { "11111", "22222" });
805
806             mock.VerifyAll();
807         }
808
809         [Fact]
810         public async Task UsersReportSpam_Test()
811         {
812             var mock = new Mock<IApiConnectionLegacy>();
813             mock.Setup(x =>
814                 x.PostLazyAsync<TwitterUser>(
815                     new Uri("users/report_spam.json", UriKind.Relative),
816                     new Dictionary<string, string>
817                     {
818                             { "screen_name", "twitterapi" },
819                             { "tweet_mode", "extended" },
820                     })
821             )
822             .ReturnsAsync(LazyJson.Create(new TwitterUser { ScreenName = "twitterapi" }));
823
824             using var twitterApi = new TwitterApi();
825             twitterApi.ApiConnection = mock.Object;
826
827             await twitterApi.UsersReportSpam(screenName: "twitterapi")
828                 .IgnoreResponse();
829
830             mock.VerifyAll();
831         }
832
833         [Fact]
834         public async Task FavoritesList_Test()
835         {
836             var mock = new Mock<IApiConnectionLegacy>();
837             mock.Setup(x =>
838                 x.GetAsync<TwitterStatus[]>(
839                     new Uri("favorites/list.json", UriKind.Relative),
840                     new Dictionary<string, string>
841                     {
842                             { "include_entities", "true" },
843                             { "include_ext_alt_text", "true" },
844                             { "tweet_mode", "extended" },
845                             { "count", "200" },
846                             { "max_id", "900" },
847                             { "since_id", "100" },
848                     },
849                     "/favorites/list")
850             )
851             .ReturnsAsync(Array.Empty<TwitterStatus>());
852
853             using var twitterApi = new TwitterApi();
854             twitterApi.ApiConnection = mock.Object;
855
856             await twitterApi.FavoritesList(200, maxId: 900L, sinceId: 100L);
857
858             mock.VerifyAll();
859         }
860
861         [Fact]
862         public async Task FavoritesCreate_Test()
863         {
864             var mock = new Mock<IApiConnectionLegacy>();
865             mock.Setup(x =>
866                 x.PostLazyAsync<TwitterStatus>(
867                     new Uri("favorites/create.json", UriKind.Relative),
868                     new Dictionary<string, string>
869                     {
870                             { "id", "100" },
871                             { "tweet_mode", "extended" },
872                     })
873             )
874             .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L }));
875
876             using var twitterApi = new TwitterApi();
877             twitterApi.ApiConnection = mock.Object;
878
879             await twitterApi.FavoritesCreate(statusId: new("100"))
880                 .IgnoreResponse();
881
882             mock.VerifyAll();
883         }
884
885         [Fact]
886         public async Task FavoritesDestroy_Test()
887         {
888             var mock = new Mock<IApiConnectionLegacy>();
889             mock.Setup(x =>
890                 x.PostLazyAsync<TwitterStatus>(
891                     new Uri("favorites/destroy.json", UriKind.Relative),
892                     new Dictionary<string, string>
893                     {
894                             { "id", "100" },
895                             { "tweet_mode", "extended" },
896                     })
897             )
898             .ReturnsAsync(LazyJson.Create(new TwitterStatus { Id = 100L }));
899
900             using var twitterApi = new TwitterApi();
901             twitterApi.ApiConnection = mock.Object;
902
903             await twitterApi.FavoritesDestroy(statusId: new("100"))
904                 .IgnoreResponse();
905
906             mock.VerifyAll();
907         }
908
909         [Fact]
910         public async Task FriendshipsShow_Test()
911         {
912             var mock = new Mock<IApiConnectionLegacy>();
913             mock.Setup(x =>
914                 x.GetAsync<TwitterFriendship>(
915                     new Uri("friendships/show.json", UriKind.Relative),
916                     new Dictionary<string, string> { { "source_screen_name", "twitter" }, { "target_screen_name", "twitterapi" } },
917                     "/friendships/show")
918             )
919             .ReturnsAsync(new TwitterFriendship());
920
921             using var twitterApi = new TwitterApi();
922             twitterApi.ApiConnection = mock.Object;
923
924             await twitterApi.FriendshipsShow(sourceScreenName: "twitter", targetScreenName: "twitterapi");
925
926             mock.VerifyAll();
927         }
928
929         [Fact]
930         public async Task FriendshipsCreate_Test()
931         {
932             var mock = new Mock<IApiConnectionLegacy>();
933             mock.Setup(x =>
934                 x.PostLazyAsync<TwitterFriendship>(
935                     new Uri("friendships/create.json", UriKind.Relative),
936                     new Dictionary<string, string> { { "screen_name", "twitterapi" } })
937             )
938             .ReturnsAsync(LazyJson.Create(new TwitterFriendship()));
939
940             using var twitterApi = new TwitterApi();
941             twitterApi.ApiConnection = mock.Object;
942
943             await twitterApi.FriendshipsCreate(screenName: "twitterapi")
944                 .IgnoreResponse();
945
946             mock.VerifyAll();
947         }
948
949         [Fact]
950         public async Task FriendshipsDestroy_Test()
951         {
952             var mock = new Mock<IApiConnectionLegacy>();
953             mock.Setup(x =>
954                 x.PostLazyAsync<TwitterFriendship>(
955                     new Uri("friendships/destroy.json", UriKind.Relative),
956                     new Dictionary<string, string> { { "screen_name", "twitterapi" } })
957             )
958             .ReturnsAsync(LazyJson.Create(new TwitterFriendship()));
959
960             using var twitterApi = new TwitterApi();
961             twitterApi.ApiConnection = mock.Object;
962
963             await twitterApi.FriendshipsDestroy(screenName: "twitterapi")
964                 .IgnoreResponse();
965
966             mock.VerifyAll();
967         }
968
969         [Fact]
970         public async Task NoRetweetIds_Test()
971         {
972             var mock = new Mock<IApiConnectionLegacy>();
973             mock.Setup(x =>
974                 x.GetAsync<long[]>(
975                     new Uri("friendships/no_retweets/ids.json", UriKind.Relative),
976                     null,
977                     "/friendships/no_retweets/ids")
978             )
979             .ReturnsAsync(Array.Empty<long>());
980
981             using var twitterApi = new TwitterApi();
982             twitterApi.ApiConnection = mock.Object;
983
984             await twitterApi.NoRetweetIds();
985
986             mock.VerifyAll();
987         }
988
989         [Fact]
990         public async Task FollowersIds_Test()
991         {
992             var mock = new Mock<IApiConnectionLegacy>();
993             mock.Setup(x =>
994                 x.GetAsync<TwitterIds>(
995                     new Uri("followers/ids.json", UriKind.Relative),
996                     new Dictionary<string, string> { { "cursor", "-1" } },
997                     "/followers/ids")
998             )
999             .ReturnsAsync(new TwitterIds());
1000
1001             using var twitterApi = new TwitterApi();
1002             twitterApi.ApiConnection = mock.Object;
1003
1004             await twitterApi.FollowersIds(cursor: -1L);
1005
1006             mock.VerifyAll();
1007         }
1008
1009         [Fact]
1010         public async Task MutesUsersIds_Test()
1011         {
1012             var mock = new Mock<IApiConnectionLegacy>();
1013             mock.Setup(x =>
1014                 x.GetAsync<TwitterIds>(
1015                     new Uri("mutes/users/ids.json", UriKind.Relative),
1016                     new Dictionary<string, string> { { "cursor", "-1" } },
1017                     "/mutes/users/ids")
1018             )
1019             .ReturnsAsync(new TwitterIds());
1020
1021             using var twitterApi = new TwitterApi();
1022             twitterApi.ApiConnection = mock.Object;
1023
1024             await twitterApi.MutesUsersIds(cursor: -1L);
1025
1026             mock.VerifyAll();
1027         }
1028
1029         [Fact]
1030         public async Task BlocksIds_Test()
1031         {
1032             var mock = new Mock<IApiConnectionLegacy>();
1033             mock.Setup(x =>
1034                 x.GetAsync<TwitterIds>(
1035                     new Uri("blocks/ids.json", UriKind.Relative),
1036                     new Dictionary<string, string> { { "cursor", "-1" } },
1037                     "/blocks/ids")
1038             )
1039             .ReturnsAsync(new TwitterIds());
1040
1041             using var twitterApi = new TwitterApi();
1042             twitterApi.ApiConnection = mock.Object;
1043
1044             await twitterApi.BlocksIds(cursor: -1L);
1045
1046             mock.VerifyAll();
1047         }
1048
1049         [Fact]
1050         public async Task BlocksCreate_Test()
1051         {
1052             var mock = new Mock<IApiConnectionLegacy>();
1053             mock.Setup(x =>
1054                 x.PostLazyAsync<TwitterUser>(
1055                     new Uri("blocks/create.json", UriKind.Relative),
1056                     new Dictionary<string, string>
1057                     {
1058                             { "screen_name", "twitterapi" },
1059                             { "tweet_mode", "extended" },
1060                     })
1061             )
1062             .ReturnsAsync(LazyJson.Create(new TwitterUser()));
1063
1064             using var twitterApi = new TwitterApi();
1065             twitterApi.ApiConnection = mock.Object;
1066
1067             await twitterApi.BlocksCreate(screenName: "twitterapi")
1068                 .IgnoreResponse();
1069
1070             mock.VerifyAll();
1071         }
1072
1073         [Fact]
1074         public async Task BlocksDestroy_Test()
1075         {
1076             var mock = new Mock<IApiConnectionLegacy>();
1077             mock.Setup(x =>
1078                 x.PostLazyAsync<TwitterUser>(
1079                     new Uri("blocks/destroy.json", UriKind.Relative),
1080                     new Dictionary<string, string>
1081                     {
1082                             { "screen_name", "twitterapi" },
1083                             { "tweet_mode", "extended" },
1084                     })
1085             )
1086             .ReturnsAsync(LazyJson.Create(new TwitterUser()));
1087
1088             using var twitterApi = new TwitterApi();
1089             twitterApi.ApiConnection = mock.Object;
1090
1091             await twitterApi.BlocksDestroy(screenName: "twitterapi")
1092                 .IgnoreResponse();
1093
1094             mock.VerifyAll();
1095         }
1096
1097         [Fact]
1098         public async Task AccountVerifyCredentials_Test()
1099         {
1100             var mock = new Mock<IApiConnectionLegacy>();
1101             mock.Setup(x =>
1102                 x.GetAsync<TwitterUser>(
1103                     new Uri("account/verify_credentials.json", UriKind.Relative),
1104                     new Dictionary<string, string>
1105                     {
1106                             { "include_entities", "true" },
1107                             { "include_ext_alt_text", "true" },
1108                             { "tweet_mode", "extended" },
1109                     },
1110                     "/account/verify_credentials")
1111             )
1112             .ReturnsAsync(new TwitterUser
1113             {
1114                 Id = 100L,
1115                 ScreenName = "opentween",
1116             });
1117
1118             using var twitterApi = new TwitterApi();
1119             twitterApi.ApiConnection = mock.Object;
1120
1121             await twitterApi.AccountVerifyCredentials();
1122
1123             Assert.Equal(100L, twitterApi.CurrentUserId);
1124             Assert.Equal("opentween", twitterApi.CurrentScreenName);
1125
1126             mock.VerifyAll();
1127         }
1128
1129         [Fact]
1130         public async Task AccountUpdateProfile_Test()
1131         {
1132             var mock = new Mock<IApiConnectionLegacy>();
1133             mock.Setup(x =>
1134                 x.PostLazyAsync<TwitterUser>(
1135                     new Uri("account/update_profile.json", UriKind.Relative),
1136                     new Dictionary<string, string>
1137                     {
1138                             { "include_entities", "true" },
1139                             { "include_ext_alt_text", "true" },
1140                             { "tweet_mode", "extended" },
1141                             { "name", "Name" },
1142                             { "url", "http://example.com/" },
1143                             { "location", "Location" },
1144                             { "description", "&lt;script&gt;alert(1)&lt;/script&gt;" },
1145                     })
1146             )
1147             .ReturnsAsync(LazyJson.Create(new TwitterUser()));
1148
1149             using var twitterApi = new TwitterApi();
1150             twitterApi.ApiConnection = mock.Object;
1151
1152             await twitterApi.AccountUpdateProfile(name: "Name", url: "http://example.com/", location: "Location", description: "<script>alert(1)</script>")
1153                 .IgnoreResponse();
1154
1155             mock.VerifyAll();
1156         }
1157
1158         [Fact]
1159         public async Task AccountUpdateProfileImage_Test()
1160         {
1161             using var image = TestUtils.CreateDummyImage();
1162             using var media = new MemoryImageMediaItem(image);
1163
1164             var mock = this.CreateApiConnectionMock<PostMultipartRequest>(r =>
1165             {
1166                 Assert.Equal(new("account/update_profile_image.json", UriKind.Relative), r.RequestUri);
1167                 var expectedQuery = new Dictionary<string, string>
1168                 {
1169                     ["include_entities"] = "true",
1170                     ["include_ext_alt_text"] = "true",
1171                     ["tweet_mode"] = "extended",
1172                 };
1173                 Assert.Equal(expectedQuery, r.Query);
1174                 var expectedMedia = new Dictionary<string, IMediaItem>
1175                 {
1176                     ["image"] = media,
1177                 };
1178                 Assert.Equal(expectedMedia, r.Media);
1179             });
1180
1181             using var twitterApi = new TwitterApi();
1182             twitterApi.ApiConnection = mock.Object;
1183
1184             await twitterApi.AccountUpdateProfileImage(media)
1185                 .IgnoreResponse();
1186
1187             mock.VerifyAll();
1188         }
1189
1190         [Fact]
1191         public async Task ApplicationRateLimitStatus_Test()
1192         {
1193             var mock = new Mock<IApiConnectionLegacy>();
1194             mock.Setup(x =>
1195                 x.GetAsync<TwitterRateLimits>(
1196                     new Uri("application/rate_limit_status.json", UriKind.Relative),
1197                     null,
1198                     "/application/rate_limit_status")
1199             )
1200             .ReturnsAsync(new TwitterRateLimits());
1201
1202             using var twitterApi = new TwitterApi();
1203             twitterApi.ApiConnection = mock.Object;
1204
1205             await twitterApi.ApplicationRateLimitStatus();
1206
1207             mock.VerifyAll();
1208         }
1209
1210         [Fact]
1211         public async Task Configuration_Test()
1212         {
1213             var mock = new Mock<IApiConnectionLegacy>();
1214             mock.Setup(x =>
1215                 x.GetAsync<TwitterConfiguration>(
1216                     new Uri("help/configuration.json", UriKind.Relative),
1217                     null,
1218                     "/help/configuration")
1219             )
1220             .ReturnsAsync(new TwitterConfiguration());
1221
1222             using var twitterApi = new TwitterApi();
1223             twitterApi.ApiConnection = mock.Object;
1224
1225             await twitterApi.Configuration();
1226
1227             mock.VerifyAll();
1228         }
1229
1230         [Fact]
1231         public async Task MediaUploadInit_Test()
1232         {
1233             var mock = new Mock<IApiConnectionLegacy>();
1234             mock.Setup(x =>
1235                 x.PostLazyAsync<TwitterUploadMediaInit>(
1236                     new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute),
1237                     new Dictionary<string, string>
1238                     {
1239                             { "command", "INIT" },
1240                             { "total_bytes", "123456" },
1241                             { "media_type", "image/png" },
1242                             { "media_category", "dm_image" },
1243                     })
1244             )
1245             .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaInit()));
1246
1247             using var twitterApi = new TwitterApi();
1248             twitterApi.ApiConnection = mock.Object;
1249
1250             await twitterApi.MediaUploadInit(totalBytes: 123456L, mediaType: "image/png", mediaCategory: "dm_image")
1251                 .IgnoreResponse();
1252
1253             mock.VerifyAll();
1254         }
1255
1256         [Fact]
1257         public async Task MediaUploadAppend_Test()
1258         {
1259             using var image = TestUtils.CreateDummyImage();
1260             using var media = new MemoryImageMediaItem(image);
1261
1262             var mock = this.CreateApiConnectionMock<PostMultipartRequest>(r =>
1263             {
1264                 Assert.Equal(new("https://upload.twitter.com/1.1/media/upload.json"), r.RequestUri);
1265                 var expectedQuery = new Dictionary<string, string>
1266                 {
1267                     ["command"] = "APPEND",
1268                     ["media_id"] = "11111",
1269                     ["segment_index"] = "1",
1270                 };
1271                 Assert.Equal(expectedQuery, r.Query);
1272                 var expectedMedia = new Dictionary<string, IMediaItem>
1273                 {
1274                     ["media"] = media,
1275                 };
1276                 Assert.Equal(expectedMedia, r.Media);
1277             });
1278
1279             using var twitterApi = new TwitterApi();
1280             twitterApi.ApiConnection = mock.Object;
1281
1282             await twitterApi.MediaUploadAppend(mediaId: 11111L, segmentIndex: 1, media: media);
1283
1284             mock.VerifyAll();
1285         }
1286
1287         [Fact]
1288         public async Task MediaUploadFinalize_Test()
1289         {
1290             var mock = new Mock<IApiConnectionLegacy>();
1291             mock.Setup(x =>
1292                 x.PostLazyAsync<TwitterUploadMediaResult>(
1293                     new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute),
1294                     new Dictionary<string, string>
1295                     {
1296                             { "command", "FINALIZE" },
1297                             { "media_id", "11111" },
1298                     })
1299             )
1300             .ReturnsAsync(LazyJson.Create(new TwitterUploadMediaResult()));
1301
1302             using var twitterApi = new TwitterApi();
1303             twitterApi.ApiConnection = mock.Object;
1304
1305             await twitterApi.MediaUploadFinalize(mediaId: 11111L)
1306                 .IgnoreResponse();
1307
1308             mock.VerifyAll();
1309         }
1310
1311         [Fact]
1312         public async Task MediaUploadStatus_Test()
1313         {
1314             var mock = new Mock<IApiConnectionLegacy>();
1315             mock.Setup(x =>
1316                 x.GetAsync<TwitterUploadMediaResult>(
1317                     new Uri("https://upload.twitter.com/1.1/media/upload.json", UriKind.Absolute),
1318                     new Dictionary<string, string>
1319                     {
1320                             { "command", "STATUS" },
1321                             { "media_id", "11111" },
1322                     },
1323                     null)
1324             )
1325             .ReturnsAsync(new TwitterUploadMediaResult());
1326
1327             using var twitterApi = new TwitterApi();
1328             twitterApi.ApiConnection = mock.Object;
1329
1330             await twitterApi.MediaUploadStatus(mediaId: 11111L);
1331
1332             mock.VerifyAll();
1333         }
1334
1335         [Fact]
1336         public async Task MediaMetadataCreate_Test()
1337         {
1338             var mock = this.CreateApiConnectionMock<PostJsonRequest>(r =>
1339             {
1340                 Assert.Equal(new("https://upload.twitter.com/1.1/media/metadata/create.json"), r.RequestUri);
1341                 Assert.Equal("""{"media_id": "12345", "alt_text": {"text": "hogehoge"}}""", r.JsonString);
1342             });
1343
1344             using var twitterApi = new TwitterApi();
1345             twitterApi.ApiConnection = mock.Object;
1346
1347             await twitterApi.MediaMetadataCreate(mediaId: 12345L, altText: "hogehoge");
1348
1349             mock.VerifyAll();
1350         }
1351     }
1352 }