OSDN Git Service

Twitter.GetTextLengthRemainメソッドからURLを抽出する処理をExtractUrlsメソッドに分割
[opentween/open-tween.git] / OpenTween.Tests / TwitterTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2013 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.Linq;
25 using System.Text;
26 using System.Text.RegularExpressions;
27 using OpenTween.Api;
28 using Xunit;
29 using Xunit.Extensions;
30
31 namespace OpenTween
32 {
33     public class TwitterTest
34     {
35         [Theory]
36         [InlineData("https://twitter.com/twitterapi/status/22634515958",
37             new[] { "22634515958" })]
38         [InlineData("<a target=\"_self\" href=\"https://t.co/aaaaaaaa\" title=\"https://twitter.com/twitterapi/status/22634515958\">twitter.com/twitterapi/stat…</a>",
39             new[] { "22634515958" })]
40         [InlineData("<a target=\"_self\" href=\"https://t.co/bU3oR95KIy\" title=\"https://twitter.com/haru067/status/224782458816692224\">https://t.co/bU3oR95KIy</a>" +
41             "<a target=\"_self\" href=\"https://t.co/bbbbbbbb\" title=\"https://twitter.com/karno/status/311081657790771200\">https://t.co/bbbbbbbb</a>",
42             new[] { "224782458816692224", "311081657790771200" })]
43         [InlineData("https://mobile.twitter.com/muji_net/status/21984934471",
44             new[] { "21984934471" })]
45         [InlineData("https://twitter.com/imgazyobuzi/status/293333871171354624/photo/1",
46             new[] { "293333871171354624" })]
47         public void StatusUrlRegexTest(string url, string[] expected)
48         {
49             var results = Twitter.StatusUrlRegex.Matches(url).Cast<Match>()
50                 .Select(x => x.Groups["StatusId"].Value).ToArray();
51
52             Assert.Equal(expected, results);
53         }
54
55         [Theory]
56         [InlineData("http://favstar.fm/users/twitterapi/status/22634515958", new[] { "22634515958" })]
57         [InlineData("http://ja.favstar.fm/users/twitterapi/status/22634515958", new[] { "22634515958" })]
58         [InlineData("http://favstar.fm/t/22634515958", new[] { "22634515958" })]
59         [InlineData("http://aclog.koba789.com/i/312485321239564288", new[] { "312485321239564288" })]
60         [InlineData("http://frtrt.net/solo_status.php?status=263483634307198977", new[] { "263483634307198977" })]
61         public void ThirdPartyStatusUrlRegexTest(string url, string[] expected)
62         {
63             var results = Twitter.ThirdPartyStatusUrlRegex.Matches(url).Cast<Match>()
64                 .Select(x => x.Groups["StatusId"].Value).ToArray();
65
66             Assert.Equal(expected, results);
67         }
68
69         [Fact]
70         public void FindTopOfReplyChainTest()
71         {
72             var posts = new Dictionary<long, PostClass>
73             {
74                 [950L] = new PostClass { StatusId = 950L, InReplyToStatusId = null }, // このツイートが末端
75                 [987L] = new PostClass { StatusId = 987L, InReplyToStatusId = 950L },
76                 [999L] = new PostClass { StatusId = 999L, InReplyToStatusId = 987L },
77                 [1000L] = new PostClass { StatusId = 1000L, InReplyToStatusId = 999L },
78             };
79             Assert.Equal(950L, Twitter.FindTopOfReplyChain(posts, 1000L).StatusId);
80             Assert.Equal(950L, Twitter.FindTopOfReplyChain(posts, 950L).StatusId);
81             Assert.Throws<ArgumentException>(() => Twitter.FindTopOfReplyChain(posts, 500L));
82
83             posts = new Dictionary<long, PostClass>
84             {
85                 // 1200L は posts の中に存在しない
86                 [1210L] = new PostClass { StatusId = 1210L, InReplyToStatusId = 1200L },
87                 [1220L] = new PostClass { StatusId = 1220L, InReplyToStatusId = 1210L },
88                 [1230L] = new PostClass { StatusId = 1230L, InReplyToStatusId = 1220L },
89             };
90             Assert.Equal(1210L, Twitter.FindTopOfReplyChain(posts, 1230L).StatusId);
91             Assert.Equal(1210L, Twitter.FindTopOfReplyChain(posts, 1210L).StatusId);
92         }
93
94         [Fact]
95         public void ParseSource_Test()
96         {
97             var sourceHtml = "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>";
98
99             var result = Twitter.ParseSource(sourceHtml);
100             Assert.Equal("Twitter Web Client", result.Item1);
101             Assert.Equal(new Uri("http://twitter.com/"), result.Item2);
102         }
103
104         [Fact]
105         public void ParseSource_PlainTextTest()
106         {
107             var sourceHtml = "web";
108
109             var result = Twitter.ParseSource(sourceHtml);
110             Assert.Equal("web", result.Item1);
111             Assert.Equal(null, result.Item2);
112         }
113
114         [Fact]
115         public void ParseSource_RelativeUriTest()
116         {
117             // 参照: https://twitter.com/kim_upsilon/status/477796052049752064
118             var sourceHtml = "<a href=\"erased_45416\" rel=\"nofollow\">erased_45416</a>";
119
120             var result = Twitter.ParseSource(sourceHtml);
121             Assert.Equal("erased_45416", result.Item1);
122             Assert.Equal(new Uri("https://twitter.com/erased_45416"), result.Item2);
123         }
124
125         [Fact]
126         public void ParseSource_EmptyTest()
127         {
128             // 参照: https://twitter.com/kim_upsilon/status/595156014032244738
129             var sourceHtml = "";
130
131             var result = Twitter.ParseSource(sourceHtml);
132             Assert.Equal("", result.Item1);
133             Assert.Equal(null, result.Item2);
134         }
135
136         [Fact]
137         public void ParseSource_NullTest()
138         {
139             string sourceHtml = null;
140
141             var result = Twitter.ParseSource(sourceHtml);
142             Assert.Equal("", result.Item1);
143             Assert.Equal(null, result.Item2);
144         }
145
146         [Fact]
147         public void ParseSource_UnescapeTest()
148         {
149             string sourceHtml = "<a href=\"http://example.com/?aaa=123&amp;bbb=456\" rel=\"nofollow\">&lt;&lt;hogehoge&gt;&gt;</a>";
150
151             var result = Twitter.ParseSource(sourceHtml);
152             Assert.Equal("<<hogehoge>>", result.Item1);
153             Assert.Equal(new Uri("http://example.com/?aaa=123&bbb=456"), result.Item2);
154         }
155
156         [Fact]
157         public void ParseSource_UnescapeNoUriTest()
158         {
159             string sourceHtml = "&lt;&lt;hogehoge&gt;&gt;";
160
161             var result = Twitter.ParseSource(sourceHtml);
162             Assert.Equal("<<hogehoge>>", result.Item1);
163             Assert.Equal(null, result.Item2);
164         }
165
166         [Fact]
167         public void GetQuoteTweetStatusIds_EntityTest()
168         {
169             var entities = new[]
170             {
171                 new TwitterEntityUrl
172                 {
173                     Url = "https://t.co/3HXq0LrbJb",
174                     ExpandedUrl = "https://twitter.com/kim_upsilon/status/599261132361072640",
175                 },
176             };
177
178             var statusIds = Twitter.GetQuoteTweetStatusIds(entities);
179             Assert.Equal(new[] { 599261132361072640L }, statusIds);
180         }
181
182         [Fact]
183         public void GetQuoteTweetStatusIds_UrlStringTest()
184         {
185             var urls = new[]
186             {
187                 "https://twitter.com/kim_upsilon/status/599261132361072640",
188             };
189
190             var statusIds = Twitter.GetQuoteTweetStatusIds(urls);
191             Assert.Equal(new[] { 599261132361072640L }, statusIds);
192         }
193
194         [Fact]
195         public void GetApiResultCount_DefaultTest()
196         {
197             var oldInstance = SettingCommon.Instance;
198             SettingCommon.Instance = new SettingCommon();
199
200             var timeline = SettingCommon.Instance.CountApi;
201             var reply = SettingCommon.Instance.CountApiReply;
202             var dm = 20;  // DMは固定値
203             var more = SettingCommon.Instance.MoreCountApi;
204             var startup = SettingCommon.Instance.FirstCountApi;
205             var favorite = SettingCommon.Instance.FavoritesCountApi;
206             var list = SettingCommon.Instance.ListCountApi;
207             var search = SettingCommon.Instance.SearchCountApi;
208             var usertl = SettingCommon.Instance.UserTimelineCountApi;
209
210             // デフォルト値チェック
211             Assert.Equal(false, SettingCommon.Instance.UseAdditionalCount);
212             Assert.Equal(60, timeline);
213             Assert.Equal(40, reply);
214             Assert.Equal(200, more);
215             Assert.Equal(100, startup);
216             Assert.Equal(40, favorite);
217             Assert.Equal(100, list);
218             Assert.Equal(100, search);
219             Assert.Equal(20, usertl);
220
221             // Timeline,Reply
222             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, false));
223             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, false));
224
225             // DM
226             Assert.Equal(dm, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.DirectMessegeRcv, false, false));
227             Assert.Equal(dm, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.DirectMessegeSnt, false, false));
228
229             // その他はTimelineと同値になる
230             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
231             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
232             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
233             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
234
235             SettingCommon.Instance = oldInstance;
236         }
237
238         [Fact]
239         public void GetApiResultCount_AdditionalCountTest()
240         {
241             var oldInstance = SettingCommon.Instance;
242             SettingCommon.Instance = new SettingCommon();
243
244             var timeline = SettingCommon.Instance.CountApi;
245             var reply = SettingCommon.Instance.CountApiReply;
246             var dm = 20;  // DMは固定値
247             var more = SettingCommon.Instance.MoreCountApi;
248             var startup = SettingCommon.Instance.FirstCountApi;
249             var favorite = SettingCommon.Instance.FavoritesCountApi;
250             var list = SettingCommon.Instance.ListCountApi;
251             var search = SettingCommon.Instance.SearchCountApi;
252             var usertl = SettingCommon.Instance.UserTimelineCountApi;
253
254             SettingCommon.Instance.UseAdditionalCount = true;
255
256             // Timeline
257             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, false));
258             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, true, false));
259             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Timeline, false, true));
260
261             // Reply
262             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, false));
263             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, true, false));
264             Assert.Equal(reply, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Reply, false, true));  //Replyの値が使われる
265
266             // DM
267             Assert.Equal(dm, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.DirectMessegeRcv, false, false));
268             Assert.Equal(dm, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.DirectMessegeSnt, false, false));
269
270             // Favorites
271             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
272             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, true, false));
273             Assert.Equal(favorite, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, true));
274
275             SettingCommon.Instance.FavoritesCountApi = 0;
276
277             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, false));
278             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, true, false));
279             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.Favorites, false, true));
280
281             // List
282             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
283             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, true, false));
284             Assert.Equal(list, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, true));
285
286             SettingCommon.Instance.ListCountApi = 0;
287
288             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, false));
289             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, true, false));
290             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.List, false, true));
291
292             // PublicSearch
293             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
294             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, true, false));
295             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, true));
296
297             SettingCommon.Instance.SearchCountApi = 0;
298
299             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, false));
300             Assert.Equal(search, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, true, false));  //MoreCountApiの値がPublicSearchの最大値に制限される
301             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.PublicSearch, false, true));
302
303             // UserTimeline
304             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
305             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, true, false));
306             Assert.Equal(usertl, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, true));
307
308             SettingCommon.Instance.UserTimelineCountApi = 0;
309
310             Assert.Equal(timeline, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, false));
311             Assert.Equal(more, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, true, false));
312             Assert.Equal(startup, Twitter.GetApiResultCount(MyCommon.WORKERTYPE.UserTimeline, false, true));
313
314             SettingCommon.Instance = oldInstance;
315         }
316
317         [Fact]
318         public void GetTextLengthRemain_Test()
319         {
320             using (var twitter = new Twitter())
321             {
322                 Assert.Equal(140, twitter.GetTextLengthRemain(""));
323                 Assert.Equal(132, twitter.GetTextLengthRemain("hogehoge"));
324             }
325         }
326
327         [Fact]
328         public void GetTextLengthRemain_DirectMessageTest()
329         {
330             using (var twitter = new Twitter())
331             {
332                 // 2015年8月から DM の文字数上限が 10,000 文字に変更された
333                 // https://twittercommunity.com/t/41348
334                 twitter.Configuration.DmTextCharacterLimit = 10000;
335
336                 Assert.Equal(10000, twitter.GetTextLengthRemain("D twitter "));
337                 Assert.Equal(9992, twitter.GetTextLengthRemain("D twitter hogehoge"));
338             }
339         }
340
341         [Fact]
342         public void GetTextLengthRemain_UrlTest()
343         {
344             using (var twitter = new Twitter())
345             {
346                 // t.co に短縮される分の文字数を考慮
347                 twitter.Configuration.ShortUrlLength = 20;
348                 Assert.Equal(120, twitter.GetTextLengthRemain("http://example.com/"));
349                 Assert.Equal(120, twitter.GetTextLengthRemain("http://example.com/hogehoge"));
350                 Assert.Equal(111, twitter.GetTextLengthRemain("hogehoge http://example.com/"));
351
352                 twitter.Configuration.ShortUrlLengthHttps = 21;
353                 Assert.Equal(119, twitter.GetTextLengthRemain("https://example.com/"));
354                 Assert.Equal(119, twitter.GetTextLengthRemain("https://example.com/hogehoge"));
355                 Assert.Equal(110, twitter.GetTextLengthRemain("hogehoge https://example.com/"));
356             }
357         }
358
359         [Fact]
360         public void GetTextLengthRemain_UrlWithoutSchemeTest()
361         {
362             using (var twitter = new Twitter())
363             {
364                 // t.co に短縮される分の文字数を考慮
365                 twitter.Configuration.ShortUrlLength = 20;
366                 Assert.Equal(120, twitter.GetTextLengthRemain("example.com"));
367                 Assert.Equal(120, twitter.GetTextLengthRemain("example.com/hogehoge"));
368                 Assert.Equal(111, twitter.GetTextLengthRemain("hogehoge example.com"));
369
370                 // スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
371                 Assert.Equal(130, twitter.GetTextLengthRemain("example.jp"));
372                 // ただし、末尾にパスが続く場合は t.co に短縮される
373                 Assert.Equal(120, twitter.GetTextLengthRemain("example.jp/hogehoge"));
374             }
375         }
376
377         [Fact]
378         public void GetTextLengthRemain_SurrogatePairTest()
379         {
380             using (var twitter = new Twitter())
381             {
382                 Assert.Equal(139, twitter.GetTextLengthRemain("🍣"));
383                 Assert.Equal(133, twitter.GetTextLengthRemain("🔥🐔🔥 焼き鳥"));
384             }
385         }
386
387         [Fact]
388         public void ExtractUrls_Test()
389         {
390             Assert.Equal(new[] { "http://example.com/" }, Twitter.ExtractUrls("http://example.com/"));
391             Assert.Equal(new[] { "http://example.com/hogehoge" }, Twitter.ExtractUrls("http://example.com/hogehoge"));
392             Assert.Equal(new[] { "http://example.com/" }, Twitter.ExtractUrls("hogehoge http://example.com/"));
393
394             Assert.Equal(new[] { "https://example.com/" }, Twitter.ExtractUrls("https://example.com/"));
395             Assert.Equal(new[] { "https://example.com/hogehoge" }, Twitter.ExtractUrls("https://example.com/hogehoge"));
396             Assert.Equal(new[] { "https://example.com/" }, Twitter.ExtractUrls("hogehoge https://example.com/"));
397
398             Assert.Equal(new[] { "example.com" }, Twitter.ExtractUrls("example.com"));
399             Assert.Equal(new[] { "example.com/hogehoge" }, Twitter.ExtractUrls("example.com/hogehoge"));
400             Assert.Equal(new[] { "example.com" }, Twitter.ExtractUrls("hogehoge example.com"));
401
402             // スキーム (http://) を省略かつ末尾が ccTLD の場合は t.co に短縮されない
403             Assert.Empty(Twitter.ExtractUrls("example.jp"));
404             // ただし、末尾にパスが続く場合は t.co に短縮される
405             Assert.Equal(new[] { "example.jp/hogehoge" }, Twitter.ExtractUrls("example.jp/hogehoge"));
406         }
407     }
408 }