OSDN Git Service

鍵垢であっても自分自身のツイートを公式RTできるように修正
[opentween/open-tween.git] / OpenTween.Tests / Models / PostClassTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 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.Reflection;
26 using System.Text;
27 using System.Threading.Tasks;
28 using Xunit;
29 using Xunit.Extensions;
30
31 namespace OpenTween.Models
32 {
33     public class PostClassTest
34     {
35         class TestPostClass : PostClass
36         {
37             protected override PostClass RetweetSource
38             {
39                 get
40                 {
41                     var retweetedId = this.RetweetedId.Value;
42
43                     return PostClassTest.TestCases.ContainsKey(retweetedId) ?
44                         PostClassTest.TestCases[retweetedId] :
45                         null;
46                 }
47             }
48         }
49
50         private static Dictionary<long, PostClass> TestCases;
51
52         public PostClassTest()
53         {
54             PostClassTest.TestCases = new Dictionary<long, PostClass>
55             {
56                 [1L] = new TestPostClass { StatusId = 1L },
57                 [2L] = new TestPostClass { StatusId = 2L, IsFav = true },
58                 [3L] = new TestPostClass { StatusId = 3L, IsFav = false, RetweetedId = 2L },
59             };
60         }
61
62         [Fact]
63         public void CloneTest()
64         {
65             var post = new PostClass();
66             var clonePost = post.Clone();
67
68             TestUtils.CheckDeepCloning(post, clonePost);
69         }
70
71         [Theory]
72         [InlineData(null,  null)]
73         [InlineData("", "")]
74         [InlineData("aaa\nbbb", "aaa bbb")]
75         public void TextSingleLineTest(string text, string expected)
76         {
77             var post = new PostClass { TextFromApi = text };
78
79             Assert.Equal(expected, post.TextSingleLine);
80         }
81
82         [Theory]
83         [InlineData(1L, false)]
84         [InlineData(2L, true)]
85         [InlineData(3L, true)]
86         public void GetIsFavTest(long statusId, bool expected)
87         {
88             Assert.Equal(expected, PostClassTest.TestCases[statusId].IsFav);
89         }
90
91         [Theory]
92         [InlineData(2L, true)]
93         [InlineData(2L, false)]
94         [InlineData(3L, true)]
95         [InlineData(3L, false)]
96         public void SetIsFavTest(long statusId, bool isFav)
97         {
98             var post = PostClassTest.TestCases[statusId];
99
100             post.IsFav = isFav;
101             Assert.Equal(isFav, post.IsFav);
102
103             if (post.RetweetedId != null)
104                 Assert.Equal(isFav, PostClassTest.TestCases[post.RetweetedId.Value].IsFav);
105         }
106
107         [Theory]
108         [InlineData(false, false, false, false, -0x01)]
109         [InlineData( true, false, false, false, 0x00)]
110         [InlineData(false,  true, false, false, 0x01)]
111         [InlineData( true,  true, false, false, 0x02)]
112         [InlineData(false, false,  true, false, 0x03)]
113         [InlineData( true, false,  true, false, 0x04)]
114         [InlineData(false,  true,  true, false, 0x05)]
115         [InlineData( true,  true,  true, false, 0x06)]
116         [InlineData(false, false, false,  true, 0x07)]
117         [InlineData( true, false, false,  true, 0x08)]
118         [InlineData(false,  true, false,  true, 0x09)]
119         [InlineData( true,  true, false,  true, 0x0A)]
120         [InlineData(false, false,  true,  true, 0x0B)]
121         [InlineData( true, false,  true,  true, 0x0C)]
122         [InlineData(false,  true,  true,  true, 0x0D)]
123         [InlineData( true,  true,  true,  true, 0x0E)]
124         public void StateIndexTest(bool protect, bool mark, bool reply, bool geo, int expected)
125         {
126             var post = new TestPostClass
127             {
128                 IsProtect = protect,
129                 IsMark = mark,
130                 InReplyToStatusId = reply ? (long?)100L : null,
131                 PostGeo = geo ? new PostClass.StatusGeo(-126.716667, -47.15) : (PostClass.StatusGeo?)null,
132             };
133
134             Assert.Equal(expected, post.StateIndex);
135         }
136
137         [Fact]
138         public void SourceHtml_Test()
139         {
140             var post = new TestPostClass
141             {
142                 Source = "Twitter Web Client",
143                 SourceUri = new Uri("http://twitter.com/"),
144             };
145
146             Assert.Equal("<a href=\"http://twitter.com/\" rel=\"nofollow\">Twitter Web Client</a>", post.SourceHtml);
147         }
148
149         [Fact]
150         public void SourceHtml_PlainTextTest()
151         {
152             var post = new TestPostClass
153             {
154                 Source = "web",
155                 SourceUri = null,
156             };
157
158             Assert.Equal("web", post.SourceHtml);
159         }
160
161         [Fact]
162         public void SourceHtml_EscapeTest()
163         {
164             var post = new TestPostClass
165             {
166                 Source = "<script>alert(1)</script>",
167                 SourceUri = new Uri("http://example.com/?aaa=123&bbb=456"),
168             };
169
170             Assert.Equal("<a href=\"http://example.com/?aaa=123&amp;bbb=456\" rel=\"nofollow\">&lt;script&gt;alert(1)&lt;/script&gt;</a>", post.SourceHtml);
171         }
172
173         [Fact]
174         public void SourceHtml_EscapePlainTextTest()
175         {
176             var post = new TestPostClass
177             {
178                 Source = "<script>alert(1)</script>",
179                 SourceUri = null,
180             };
181
182             Assert.Equal("&lt;script&gt;alert(1)&lt;/script&gt;", post.SourceHtml);
183         }
184
185         [Fact]
186         public void DeleteTest()
187         {
188             var post = new TestPostClass
189             {
190                 InReplyToStatusId = 10L,
191                 InReplyToUser = "hogehoge",
192                 InReplyToUserId = 100L,
193                 IsReply = true,
194                 ReplyToList = new List<string> {"hogehoge"},
195             };
196
197             post.IsDeleted = true;
198
199             Assert.Null(post.InReplyToStatusId);
200             Assert.Equal("", post.InReplyToUser);
201             Assert.Null(post.InReplyToUserId);
202             Assert.False(post.IsReply);
203             Assert.Empty(post.ReplyToList);
204             Assert.Equal(-1, post.StateIndex);
205         }
206
207         [Fact]
208         public void CanDeleteBy_SentDMTest()
209         {
210             var post = new TestPostClass
211             {
212                 IsDm = true,
213                 IsMe = true, // 自分が送信した DM
214                 UserId = 222L, // 送信先ユーザーID
215             };
216
217             Assert.True(post.CanDeleteBy(selfUserId: 111L));
218         }
219
220         [Fact]
221         public void CanDeleteBy_ReceivedDMTest()
222         {
223             var post = new TestPostClass
224             {
225                 IsDm = true,
226                 IsMe = false, // 自分が受け取った DM
227                 UserId = 222L, // 送信元ユーザーID
228             };
229
230             Assert.True(post.CanDeleteBy(selfUserId: 111L));
231         }
232
233         [Fact]
234         public void CanDeleteBy_MyTweetTest()
235         {
236             var post = new TestPostClass
237             {
238                 UserId = 111L, // 自分のツイート
239             };
240
241             Assert.True(post.CanDeleteBy(selfUserId: 111L));
242         }
243
244         [Fact]
245         public void CanDeleteBy_OthersTweetTest()
246         {
247             var post = new TestPostClass
248             {
249                 UserId = 222L, // 他人のツイート
250             };
251
252             Assert.False(post.CanDeleteBy(selfUserId: 111L));
253         }
254
255         [Fact]
256         public void CanDeleteBy_RetweetedByMeTest()
257         {
258             var post = new TestPostClass
259             {
260                 RetweetedByUserId = 111L, // 自分がリツイートした
261                 UserId = 222L, // 他人のツイート
262             };
263
264             Assert.True(post.CanDeleteBy(selfUserId: 111L));
265         }
266
267         [Fact]
268         public void CanDeleteBy_RetweetedByOthersTest()
269         {
270             var post = new TestPostClass
271             {
272                 RetweetedByUserId = 333L, // 他人がリツイートした
273                 UserId = 222L, // 他人のツイート
274             };
275
276             Assert.False(post.CanDeleteBy(selfUserId: 111L));
277         }
278
279         [Fact]
280         public void CanDeleteBy_MyTweetHaveBeenRetweetedByOthersTest()
281         {
282             var post = new TestPostClass
283             {
284                 RetweetedByUserId = 222L, // 他人がリツイートした
285                 UserId = 111L, // 自分のツイート
286             };
287
288             Assert.True(post.CanDeleteBy(selfUserId: 111L));
289         }
290
291         [Fact]
292         public void CanRetweetBy_DMTest()
293         {
294             var post = new TestPostClass
295             {
296                 IsDm = true,
297                 IsMe = false, // 自分が受け取った DM
298                 UserId = 222L, // 送信元ユーザーID
299             };
300
301             Assert.False(post.CanRetweetBy(selfUserId: 111L));
302         }
303
304         [Fact]
305         public void CanRetweetBy_MyTweetTest()
306         {
307             var post = new TestPostClass
308             {
309                 UserId = 111L, // 自分のツイート
310             };
311
312             Assert.True(post.CanRetweetBy(selfUserId: 111L));
313         }
314
315         [Fact]
316         public void CanRetweetBy_ProtectedMyTweetTest()
317         {
318             var post = new TestPostClass
319             {
320                 UserId = 111L, // 自分のツイート
321                 IsProtect = true,
322             };
323
324             Assert.True(post.CanRetweetBy(selfUserId: 111L));
325         }
326
327         [Fact]
328         public void CanRetweetBy_OthersTweet_NotProtectedTest()
329         {
330             var post = new TestPostClass
331             {
332                 UserId = 222L, // 他人のツイート
333                 IsProtect = false,
334             };
335
336             Assert.True(post.CanRetweetBy(selfUserId: 111L));
337         }
338
339         [Fact]
340         public void CanRetweetBy_OthersTweet_ProtectedTest()
341         {
342             var post = new TestPostClass
343             {
344                 UserId = 222L, // 他人のツイート
345                 IsProtect = true,
346             };
347
348             Assert.False(post.CanRetweetBy(selfUserId: 111L));
349         }
350
351         [Fact]
352         public void ConvertToOriginalPost_Test()
353         {
354             var retweetPost = new PostClass
355             {
356                 StatusId = 100L,
357                 ScreenName = "@aaa",
358                 UserId = 1L,
359
360                 RetweetedId = 50L,
361                 RetweetedBy = "@bbb",
362                 RetweetedByUserId = 2L,
363                 RetweetedCount = 0,
364             };
365
366             var originalPost = retweetPost.ConvertToOriginalPost();
367
368             Assert.Equal(50L, originalPost.StatusId);
369             Assert.Equal("@aaa", originalPost.ScreenName);
370             Assert.Equal(1L, originalPost.UserId);
371
372             Assert.Equal(null, originalPost.RetweetedId);
373             Assert.Equal("", originalPost.RetweetedBy);
374             Assert.Equal(null, originalPost.RetweetedByUserId);
375             Assert.Equal(1, originalPost.RetweetedCount);
376         }
377
378         [Fact]
379         public void ConvertToOriginalPost_ErrorTest()
380         {
381             // 公式 RT でないツイート
382             var post = new PostClass { StatusId = 100L, RetweetedId = null };
383
384             Assert.Throws<InvalidOperationException>(() => post.ConvertToOriginalPost());
385         }
386
387         class FakeExpandedUrlInfo : PostClass.ExpandedUrlInfo
388         {
389             public TaskCompletionSource<string> fakeResult;
390
391             public FakeExpandedUrlInfo(string url, string expandedUrl, bool deepExpand)
392                 : base(url, expandedUrl, deepExpand)
393             {
394             }
395
396             protected override async Task DeepExpandAsync()
397             {
398                 this.fakeResult = new TaskCompletionSource<string>();
399                 this._expandedUrl = await this.fakeResult.Task;
400             }
401         }
402
403         [Fact]
404         public async Task ExpandedUrls_BasicScenario()
405         {
406             var post = new PostClass
407             {
408                 Text = "<a href=\"http://t.co/aaaaaaa\" title=\"http://t.co/aaaaaaa\">bit.ly/abcde</a>",
409                 ExpandedUrls = new[]
410                 {
411                     new FakeExpandedUrlInfo(
412                         // 展開前の t.co ドメインの URL
413                         url:  "http://t.co/aaaaaaa",
414
415                         // Entity の expanded_url に含まれる URL
416                         expandedUrl: "http://bit.ly/abcde",
417
418                         // expandedUrl をさらに ShortUrl クラスで再帰的に展開する
419                         deepExpand: true
420                     ),
421                 },
422             };
423
424             var urlInfo = (FakeExpandedUrlInfo)post.ExpandedUrls.Single();
425
426             // ExpandedUrlInfo による展開が完了していない状態
427             //   → この段階では Entity に含まれる expanded_url の URL が使用される
428             Assert.False(urlInfo.ExpandedCompleted);
429             Assert.Equal("http://bit.ly/abcde", urlInfo.ExpandedUrl);
430             Assert.Equal("http://bit.ly/abcde", post.GetExpandedUrl("http://t.co/aaaaaaa"));
431             Assert.Equal(new[] { "http://bit.ly/abcde" }, post.GetExpandedUrls());
432             Assert.Equal("<a href=\"http://t.co/aaaaaaa\" title=\"http://bit.ly/abcde\">bit.ly/abcde</a>", post.Text);
433
434             // bit.ly 展開後の URL は「http://example.com/abcde」
435             urlInfo.fakeResult.SetResult("http://example.com/abcde");
436             await urlInfo.ExpandTask;
437
438             // ExpandedUrlInfo による展開が完了した後の状態
439             //   → 再帰的な展開後の URL が使用される
440             Assert.True(urlInfo.ExpandedCompleted);
441             Assert.Equal("http://example.com/abcde", urlInfo.ExpandedUrl);
442             Assert.Equal("http://example.com/abcde", post.GetExpandedUrl("http://t.co/aaaaaaa"));
443             Assert.Equal(new[] { "http://example.com/abcde" }, post.GetExpandedUrls());
444             Assert.Equal("<a href=\"http://t.co/aaaaaaa\" title=\"http://example.com/abcde\">bit.ly/abcde</a>", post.Text);
445         }
446     }
447 }