OSDN Git Service

GitHub Actionsで使用するactionをアップデート
[opentween/open-tween.git] / OpenTween.Tests / TweetThumbnailTest.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.Net.Http;
26 using System.Text;
27 using System.Threading;
28 using System.Threading.Tasks;
29 using Moq;
30 using OpenTween.Models;
31 using OpenTween.Thumbnail;
32 using OpenTween.Thumbnail.Services;
33 using Xunit;
34
35 namespace OpenTween
36 {
37     public class TweetThumbnailTest
38     {
39         private ThumbnailGenerator CreateThumbnailGenerator()
40         {
41             var imgAzyobuziNet = new ImgAzyobuziNet(autoupdate: false);
42             var thumbGenerator = new ThumbnailGenerator(imgAzyobuziNet);
43             thumbGenerator.Services.Clear();
44             return thumbGenerator;
45         }
46
47         private IThumbnailService CreateThumbnailService()
48         {
49             var thumbnailServiceMock = new Mock<IThumbnailService>();
50             thumbnailServiceMock
51                 .Setup(
52                     x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
53                 )
54                 .ReturnsAsync(new MockThumbnailInfo
55                 {
56                     MediaPageUrl = "http://example.com/abcd",
57                     ThumbnailImageUrl = "http://img.example.com/abcd.png",
58                 });
59             thumbnailServiceMock
60                 .Setup(
61                     x => x.GetThumbnailInfoAsync("http://example.com/efgh", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
62                 )
63                 .ReturnsAsync(new MockThumbnailInfo
64                 {
65                     MediaPageUrl = "http://example.com/efgh",
66                     ThumbnailImageUrl = "http://img.example.com/efgh.png",
67                 });
68             return thumbnailServiceMock.Object;
69         }
70
71         [Fact]
72         public async Task PrepareThumbnails_Test()
73         {
74             var thumbnailGenerator = this.CreateThumbnailGenerator();
75             thumbnailGenerator.Services.Add(this.CreateThumbnailService());
76
77             var tweetThumbnail = new TweetThumbnail();
78             tweetThumbnail.Initialize(thumbnailGenerator);
79
80             var post = new PostClass
81             {
82                 StatusId = new TwitterStatusId("100"),
83                 Media = new() { new("http://example.com/abcd") },
84             };
85
86             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
87
88             Assert.True(tweetThumbnail.ThumbnailAvailable);
89             Assert.Single(tweetThumbnail.Thumbnails);
90             Assert.Equal(0, tweetThumbnail.SelectedIndex);
91             Assert.Equal("http://example.com/abcd", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
92             Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
93         }
94
95         [Fact]
96         public async Task PrepareThumbnails_NoThumbnailTest()
97         {
98             var thumbnailGenerator = this.CreateThumbnailGenerator();
99             thumbnailGenerator.Services.Add(this.CreateThumbnailService());
100
101             var tweetThumbnail = new TweetThumbnail();
102             tweetThumbnail.Initialize(thumbnailGenerator);
103
104             var post = new PostClass
105             {
106                 StatusId = new TwitterStatusId("100"),
107                 Media = new() { new("http://hoge.example.com/") },
108             };
109
110             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
111
112             Assert.False(tweetThumbnail.ThumbnailAvailable);
113             Assert.Throws<InvalidOperationException>(() => tweetThumbnail.Thumbnails);
114         }
115
116         [Fact]
117         public async Task PrepareThumbnails_CancelTest()
118         {
119             var thumbnailServiceMock = new Mock<IThumbnailService>();
120             thumbnailServiceMock
121                 .Setup(
122                     x => x.GetThumbnailInfoAsync("http://slow.example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
123                 )
124                 .Returns(async () =>
125                 {
126                     await Task.Delay(200);
127                     return new MockThumbnailInfo();
128                 });
129
130             var thumbnailGenerator = this.CreateThumbnailGenerator();
131             thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
132
133             var tweetThumbnail = new TweetThumbnail();
134             tweetThumbnail.Initialize(thumbnailGenerator);
135
136             var post = new PostClass
137             {
138                 StatusId = new TwitterStatusId("100"),
139                 Media = new() { new("http://slow.example.com/abcd") },
140             };
141
142             using var tokenSource = new CancellationTokenSource();
143             var task = tweetThumbnail.PrepareThumbnails(post, tokenSource.Token);
144             tokenSource.Cancel();
145
146             await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await task);
147             Assert.True(task.IsCanceled);
148         }
149
150         [Fact]
151         public async Task LoadSelectedThumbnail_Test()
152         {
153             using var image = TestUtils.CreateDummyImage();
154             var thumbnailInfoMock = new Mock<ThumbnailInfo>() { CallBase = true };
155             thumbnailInfoMock
156                 .Setup(
157                     x => x.LoadThumbnailImageAsync(It.IsAny<HttpClient>(), It.IsAny<CancellationToken>())
158                 )
159                 .ReturnsAsync(image);
160
161             var thumbnailServiceMock = new Mock<IThumbnailService>();
162             thumbnailServiceMock
163                 .Setup(
164                     x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
165                 )
166                 .ReturnsAsync(thumbnailInfoMock.Object);
167
168             var thumbnailGenerator = this.CreateThumbnailGenerator();
169             thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
170
171             var tweetThumbnail = new TweetThumbnail();
172             tweetThumbnail.Initialize(thumbnailGenerator);
173
174             var post = new PostClass
175             {
176                 StatusId = new TwitterStatusId("100"),
177                 Media = new() { new("http://example.com/abcd") },
178             };
179
180             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
181
182             var loadedImage = await tweetThumbnail.LoadSelectedThumbnail();
183             Assert.Same(image, loadedImage);
184         }
185
186         [Fact]
187         public async Task LoadSelectedThumbnail_RequestCollapsingTest()
188         {
189             var tsc = new TaskCompletionSource<MemoryImage>();
190             var thumbnailInfoMock = new Mock<ThumbnailInfo>() { CallBase = true };
191             thumbnailInfoMock
192                 .Setup(
193                     x => x.LoadThumbnailImageAsync(It.IsAny<HttpClient>(), It.IsAny<CancellationToken>())
194                 )
195                 .Returns(tsc.Task);
196
197             var thumbnailServiceMock = new Mock<IThumbnailService>();
198             thumbnailServiceMock
199                 .Setup(
200                     x => x.GetThumbnailInfoAsync("http://example.com/abcd", It.IsAny<PostClass>(), It.IsAny<CancellationToken>())
201                 )
202                 .ReturnsAsync(thumbnailInfoMock.Object);
203
204             var thumbnailGenerator = this.CreateThumbnailGenerator();
205             thumbnailGenerator.Services.Add(thumbnailServiceMock.Object);
206
207             var tweetThumbnail = new TweetThumbnail();
208             tweetThumbnail.Initialize(thumbnailGenerator);
209
210             var post = new PostClass
211             {
212                 StatusId = new TwitterStatusId("100"),
213                 Media = new() { new("http://example.com/abcd") },
214             };
215
216             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
217
218             var loadTask1 = tweetThumbnail.LoadSelectedThumbnail();
219             await Task.Delay(50);
220
221             // 画像のロードが完了しない間に再度 LoadSelectedThumbnail が呼ばれた場合は同一の Task を返す
222             // (複数回呼ばれても画像のリクエストは一本にまとめられる)
223             var loadTask2 = tweetThumbnail.LoadSelectedThumbnail();
224             Assert.Same(loadTask1, loadTask2);
225
226             using var image = TestUtils.CreateDummyImage();
227             tsc.SetResult(image);
228
229             Assert.Same(image, await loadTask1);
230         }
231
232         [Fact]
233         public async Task SelectedIndex_Test()
234         {
235             var thumbnailGenerator = this.CreateThumbnailGenerator();
236             thumbnailGenerator.Services.Add(this.CreateThumbnailService());
237
238             var tweetThumbnail = new TweetThumbnail();
239             tweetThumbnail.Initialize(thumbnailGenerator);
240
241             var post = new PostClass
242             {
243                 StatusId = new TwitterStatusId("100"),
244                 Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") },
245             };
246
247             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
248
249             Assert.Equal(2, tweetThumbnail.Thumbnails.Length);
250             Assert.Equal(0, tweetThumbnail.SelectedIndex);
251             Assert.Equal("http://example.com/abcd", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
252
253             tweetThumbnail.SelectedIndex = 1;
254             Assert.Equal(1, tweetThumbnail.SelectedIndex);
255             Assert.Equal("http://example.com/efgh", tweetThumbnail.CurrentThumbnail.MediaPageUrl);
256
257             Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = -1);
258             Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = 2);
259         }
260
261         [Fact]
262         public void SelectedIndex_NoThumbnailTest()
263         {
264             var thumbnailGenerator = this.CreateThumbnailGenerator();
265             var tweetThumbnail = new TweetThumbnail();
266             tweetThumbnail.Initialize(thumbnailGenerator);
267
268             Assert.False(tweetThumbnail.ThumbnailAvailable);
269
270             // サムネイルが無い場合に 0 以外の値をセットすると例外を発生させる
271             tweetThumbnail.SelectedIndex = 0;
272             Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = -1);
273             Assert.Throws<ArgumentOutOfRangeException>(() => tweetThumbnail.SelectedIndex = 1);
274         }
275
276         [Fact]
277         public async Task GetImageSearchUriGoogle_Test()
278         {
279             var thumbnailGenerator = this.CreateThumbnailGenerator();
280             thumbnailGenerator.Services.Add(this.CreateThumbnailService());
281
282             var tweetThumbnail = new TweetThumbnail();
283             tweetThumbnail.Initialize(thumbnailGenerator);
284
285             var post = new PostClass
286             {
287                 StatusId = new TwitterStatusId("100"),
288                 Media = new() { new("http://example.com/abcd") },
289             };
290
291             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
292
293             Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
294             Assert.Equal(
295                 new(@"https://lens.google.com/uploadbyurl?url=http%3A%2F%2Fimg.example.com%2Fabcd.png"),
296                 tweetThumbnail.GetImageSearchUriGoogle()
297             );
298         }
299
300         [Fact]
301         public async Task GetImageSearchUriSauceNao_Test()
302         {
303             var thumbnailGenerator = this.CreateThumbnailGenerator();
304             thumbnailGenerator.Services.Add(this.CreateThumbnailService());
305
306             var tweetThumbnail = new TweetThumbnail();
307             tweetThumbnail.Initialize(thumbnailGenerator);
308
309             var post = new PostClass
310             {
311                 StatusId = new TwitterStatusId("100"),
312                 Media = new() { new("http://example.com/abcd") },
313             };
314
315             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
316
317             Assert.Equal("http://img.example.com/abcd.png", tweetThumbnail.CurrentThumbnail.ThumbnailImageUrl);
318             Assert.Equal(
319                 new(@"https://saucenao.com/search.php?url=http%3A%2F%2Fimg.example.com%2Fabcd.png"),
320                 tweetThumbnail.GetImageSearchUriSauceNao()
321             );
322         }
323
324         [Fact]
325         public async Task Scroll_Test()
326         {
327             var thumbnailGenerator = this.CreateThumbnailGenerator();
328             thumbnailGenerator.Services.Add(this.CreateThumbnailService());
329
330             var tweetThumbnail = new TweetThumbnail();
331             tweetThumbnail.Initialize(thumbnailGenerator);
332
333             var post = new PostClass
334             {
335                 StatusId = new TwitterStatusId("100"),
336                 Media = new() { new("http://example.com/abcd"), new("http://example.com/efgh") },
337             };
338
339             await tweetThumbnail.PrepareThumbnails(post, CancellationToken.None);
340
341             Assert.Equal(2, tweetThumbnail.Thumbnails.Length);
342             Assert.Equal(0, tweetThumbnail.SelectedIndex);
343
344             tweetThumbnail.ScrollDown();
345             Assert.Equal(1, tweetThumbnail.SelectedIndex);
346
347             tweetThumbnail.ScrollDown();
348             Assert.Equal(1, tweetThumbnail.SelectedIndex);
349
350             tweetThumbnail.ScrollUp();
351             Assert.Equal(0, tweetThumbnail.SelectedIndex);
352
353             tweetThumbnail.ScrollUp();
354             Assert.Equal(0, tweetThumbnail.SelectedIndex);
355         }
356
357         private class MockThumbnailInfo : ThumbnailInfo
358         {
359             public override Task<MemoryImage> LoadThumbnailImageAsync(HttpClient http, CancellationToken cancellationToken)
360                 => Task.FromResult(TestUtils.CreateDummyImage());
361         }
362     }
363 }