OSDN Git Service

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