1 // OpenTween - Client of Twitter
2 // Copyright (c) 2012 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
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)
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
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.
23 using System.Collections.Generic;
25 using System.Reflection;
27 using System.Threading.Tasks;
29 using Xunit.Extensions;
31 namespace OpenTween.Models
33 public class PostClassTest
35 class TestPostClass : PostClass
37 protected override PostClass RetweetSource
41 var retweetedId = this.RetweetedId!.Value;
43 return PostClassTest.TestCases[retweetedId];
48 private static Dictionary<long, PostClass> TestCases;
50 public PostClassTest()
52 PostClassTest.TestCases = new Dictionary<long, PostClass>
54 [1L] = new TestPostClass { StatusId = 1L },
55 [2L] = new TestPostClass { StatusId = 2L, IsFav = true },
56 [3L] = new TestPostClass { StatusId = 3L, IsFav = false, RetweetedId = 2L },
61 public void CloneTest()
63 var post = new PostClass();
64 var clonePost = post.Clone();
66 TestUtils.CheckDeepCloning(post, clonePost);
71 [InlineData("aaa\nbbb", "aaa bbb")]
72 public void TextSingleLineTest(string text, string expected)
74 var post = new PostClass { TextFromApi = text };
76 Assert.Equal(expected, post.TextSingleLine);
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);
87 [InlineData(2L, true)]
88 [InlineData(2L, false)]
89 [InlineData(3L, true)]
90 [InlineData(3L, false)]
91 public void SetIsFavTest(long statusId, bool isFav)
93 var post = PostClassTest.TestCases[statusId];
96 Assert.Equal(isFav, post.IsFav);
98 if (post.RetweetedId != null)
99 Assert.Equal(isFav, PostClassTest.TestCases[post.RetweetedId.Value].IsFav);
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)
121 var post = new TestPostClass
125 InReplyToStatusId = reply ? (long?)100L : null,
126 PostGeo = geo ? new PostClass.StatusGeo(-126.716667, -47.15) : (PostClass.StatusGeo?)null,
129 Assert.Equal(expected, post.StateIndex);
133 public void SourceHtml_Test()
135 var post = new TestPostClass
137 Source = "Twitter Web Client",
138 SourceUri = new Uri("http://twitter.com/"),
141 Assert.Equal("<a href=\"http://twitter.com/\" rel=\"nofollow\">Twitter Web Client</a>", post.SourceHtml);
145 public void SourceHtml_PlainTextTest()
147 var post = new TestPostClass
153 Assert.Equal("web", post.SourceHtml);
157 public void SourceHtml_EscapeTest()
159 var post = new TestPostClass
161 Source = "<script>alert(1)</script>",
162 SourceUri = new Uri("http://example.com/?aaa=123&bbb=456"),
165 Assert.Equal("<a href=\"http://example.com/?aaa=123&bbb=456\" rel=\"nofollow\"><script>alert(1)</script></a>", post.SourceHtml);
169 public void SourceHtml_EscapePlainTextTest()
171 var post = new TestPostClass
173 Source = "<script>alert(1)</script>",
177 Assert.Equal("<script>alert(1)</script>", post.SourceHtml);
181 public void DeleteTest()
183 var post = new TestPostClass
185 InReplyToStatusId = 10L,
186 InReplyToUser = "hogehoge",
187 InReplyToUserId = 100L,
189 ReplyToList = { (100L, "hogehoge") },
192 post.IsDeleted = true;
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);
203 public void CanDeleteBy_SentDMTest()
205 var post = new TestPostClass
208 IsMe = true, // 自分が送信した DM
209 UserId = 222L, // 送信先ユーザーID
212 Assert.True(post.CanDeleteBy(selfUserId: 111L));
216 public void CanDeleteBy_ReceivedDMTest()
218 var post = new TestPostClass
221 IsMe = false, // 自分が受け取った DM
222 UserId = 222L, // 送信元ユーザーID
225 Assert.True(post.CanDeleteBy(selfUserId: 111L));
229 public void CanDeleteBy_MyTweetTest()
231 var post = new TestPostClass
233 UserId = 111L, // 自分のツイート
236 Assert.True(post.CanDeleteBy(selfUserId: 111L));
240 public void CanDeleteBy_OthersTweetTest()
242 var post = new TestPostClass
244 UserId = 222L, // 他人のツイート
247 Assert.False(post.CanDeleteBy(selfUserId: 111L));
251 public void CanDeleteBy_RetweetedByMeTest()
253 var post = new TestPostClass
255 RetweetedByUserId = 111L, // 自分がリツイートした
256 UserId = 222L, // 他人のツイート
259 Assert.True(post.CanDeleteBy(selfUserId: 111L));
263 public void CanDeleteBy_RetweetedByOthersTest()
265 var post = new TestPostClass
267 RetweetedByUserId = 333L, // 他人がリツイートした
268 UserId = 222L, // 他人のツイート
271 Assert.False(post.CanDeleteBy(selfUserId: 111L));
275 public void CanDeleteBy_MyTweetHaveBeenRetweetedByOthersTest()
277 var post = new TestPostClass
279 RetweetedByUserId = 222L, // 他人がリツイートした
280 UserId = 111L, // 自分のツイート
283 Assert.True(post.CanDeleteBy(selfUserId: 111L));
287 public void CanRetweetBy_DMTest()
289 var post = new TestPostClass
292 IsMe = false, // 自分が受け取った DM
293 UserId = 222L, // 送信元ユーザーID
296 Assert.False(post.CanRetweetBy(selfUserId: 111L));
300 public void CanRetweetBy_MyTweetTest()
302 var post = new TestPostClass
304 UserId = 111L, // 自分のツイート
307 Assert.True(post.CanRetweetBy(selfUserId: 111L));
311 public void CanRetweetBy_ProtectedMyTweetTest()
313 var post = new TestPostClass
315 UserId = 111L, // 自分のツイート
319 Assert.True(post.CanRetweetBy(selfUserId: 111L));
323 public void CanRetweetBy_OthersTweet_NotProtectedTest()
325 var post = new TestPostClass
327 UserId = 222L, // 他人のツイート
331 Assert.True(post.CanRetweetBy(selfUserId: 111L));
335 public void CanRetweetBy_OthersTweet_ProtectedTest()
337 var post = new TestPostClass
339 UserId = 222L, // 他人のツイート
343 Assert.False(post.CanRetweetBy(selfUserId: 111L));
347 public void ConvertToOriginalPost_Test()
349 var retweetPost = new PostClass
356 RetweetedBy = "@bbb",
357 RetweetedByUserId = 2L,
361 var originalPost = retweetPost.ConvertToOriginalPost();
363 Assert.Equal(50L, originalPost.StatusId);
364 Assert.Equal("@aaa", originalPost.ScreenName);
365 Assert.Equal(1L, originalPost.UserId);
367 Assert.Null(originalPost.RetweetedId);
368 Assert.Equal("", originalPost.RetweetedBy);
369 Assert.Null(originalPost.RetweetedByUserId);
370 Assert.Equal(1, originalPost.RetweetedCount);
374 public void ConvertToOriginalPost_ErrorTest()
377 var post = new PostClass { StatusId = 100L, RetweetedId = null };
379 Assert.Throws<InvalidOperationException>(() => post.ConvertToOriginalPost());
382 class FakeExpandedUrlInfo : PostClass.ExpandedUrlInfo
384 public TaskCompletionSource<string> fakeResult = new TaskCompletionSource<string>();
386 public FakeExpandedUrlInfo(string url, string expandedUrl, bool deepExpand)
387 : base(url, expandedUrl, deepExpand)
391 protected override async Task DeepExpandAsync()
392 => this._expandedUrl = await this.fakeResult.Task;
396 public async Task ExpandedUrls_BasicScenario()
398 var post = new PostClass
400 Text = "<a href=\"http://t.co/aaaaaaa\" title=\"http://t.co/aaaaaaa\">bit.ly/abcde</a>",
403 new FakeExpandedUrlInfo(
404 // 展開前の t.co ドメインの URL
405 url: "http://t.co/aaaaaaa",
407 // Entity の expanded_url に含まれる URL
408 expandedUrl: "http://bit.ly/abcde",
410 // expandedUrl をさらに ShortUrl クラスで再帰的に展開する
416 var urlInfo = (FakeExpandedUrlInfo)post.ExpandedUrls.Single();
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);
426 // bit.ly 展開後の URL は「http://example.com/abcde」
427 urlInfo.fakeResult.SetResult("http://example.com/abcde");
428 await urlInfo.ExpandTask;
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);